From 5682dc52b6c40f4271c2d5baa2b2e6c5028c69b3 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Thu, 6 Jun 2024 16:54:06 +0530 Subject: [PATCH 01/15] Update pac4j version --- extensions-core/druid-pac4j/pom.xml | 9 +++++-- .../druid/security/pac4j/Pac4jFilter.java | 21 ++++++++-------- .../security/pac4j/Pac4jSessionStore.java | 24 +++++++++---------- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/extensions-core/druid-pac4j/pom.xml b/extensions-core/druid-pac4j/pom.xml index dd73cb18edd8..74be55054e52 100644 --- a/extensions-core/druid-pac4j/pom.xml +++ b/extensions-core/druid-pac4j/pom.xml @@ -34,12 +34,12 @@ - 4.5.7 + 5.7.3 1.7 9.37.2 - 8.22 + 10.1 @@ -127,6 +127,11 @@ org.pac4j pac4j-core + ${pac4j.version} + + + org.pac4j + pac4j-javaee ${pac4j.version} diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java index db7d5affe90c..9464f6aa0830 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java @@ -24,14 +24,14 @@ import org.apache.druid.server.security.AuthConfig; import org.apache.druid.server.security.AuthenticationResult; import org.pac4j.core.config.Config; -import org.pac4j.core.context.JEEContext; import org.pac4j.core.context.session.SessionStore; import org.pac4j.core.engine.CallbackLogic; import org.pac4j.core.engine.DefaultCallbackLogic; import org.pac4j.core.engine.DefaultSecurityLogic; import org.pac4j.core.engine.SecurityLogic; -import org.pac4j.core.http.adapter.JEEHttpActionAdapter; import org.pac4j.core.profile.UserProfile; +import org.pac4j.jee.context.JEEContext; +import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -50,9 +50,9 @@ public class Pac4jFilter implements Filter private static final Logger LOGGER = new Logger(Pac4jFilter.class); private final Config pac4jConfig; - private final SecurityLogic securityLogic; - private final CallbackLogic callbackLogic; - private final SessionStore sessionStore; + private final SecurityLogic securityLogic; + private final CallbackLogic callbackLogic; + private final SessionStore sessionStore; private final String name; private final String authorizerName; @@ -60,8 +60,8 @@ public class Pac4jFilter implements Filter public Pac4jFilter(String name, String authorizerName, Config pac4jConfig, String cookiePassphrase) { this.pac4jConfig = pac4jConfig; - this.securityLogic = new DefaultSecurityLogic<>(); - this.callbackLogic = new DefaultCallbackLogic<>(); + this.securityLogic = new DefaultSecurityLogic(); + this.callbackLogic = new DefaultCallbackLogic(); this.name = name; this.authorizerName = authorizerName; @@ -88,18 +88,19 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; - JEEContext context = new JEEContext(httpServletRequest, httpServletResponse, sessionStore); + JEEContext context = new JEEContext(httpServletRequest, httpServletResponse); if (Pac4jCallbackResource.SELF_URL.equals(httpServletRequest.getRequestURI())) { callbackLogic.perform( context, + sessionStore, pac4jConfig, JEEHttpActionAdapter.INSTANCE, - "/", - true, false, false, null); + "/", true, null); } else { UserProfile profile = (UserProfile) securityLogic.perform( context, + sessionStore, pac4jConfig, (JEEContext ctx, Collection profiles, Object... parameters) -> { if (profiles.isEmpty()) { diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java index b0187d5e7293..9a9d71ef440d 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java @@ -23,14 +23,14 @@ import org.apache.druid.crypto.CryptoService; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.logger.Logger; -import org.pac4j.core.context.ContextHelper; import org.pac4j.core.context.Cookie; import org.pac4j.core.context.WebContext; +import org.pac4j.core.context.WebContextHelper; import org.pac4j.core.context.session.SessionStore; import org.pac4j.core.exception.TechnicalException; import org.pac4j.core.profile.CommonProfile; -import org.pac4j.core.util.JavaSerializationHelper; import org.pac4j.core.util.Pac4jConstants; +import org.pac4j.core.util.serializer.JavaSerializer; import javax.annotation.Nullable; import java.io.ByteArrayInputStream; @@ -46,19 +46,19 @@ * Code here is slight adaptation from KnoxSessionStore * for storing oauth session information in cookies. */ -public class Pac4jSessionStore implements SessionStore +public class Pac4jSessionStore implements SessionStore { private static final Logger LOGGER = new Logger(Pac4jSessionStore.class); public static final String PAC4J_SESSION_PREFIX = "pac4j.session."; - private final JavaSerializationHelper javaSerializationHelper; + private final JavaSerializer javaSerializer; private final CryptoService cryptoService; public Pac4jSessionStore(String cookiePassphrase) { - javaSerializationHelper = new JavaSerializationHelper(); + javaSerializer = new JavaSerializer(); cryptoService = new CryptoService( cookiePassphrase, "AES", @@ -72,16 +72,16 @@ public Pac4jSessionStore(String cookiePassphrase) } @Override - public String getOrCreateSessionId(WebContext context) + public Optional getSessionId(WebContext webContext, boolean b) { - return null; + return Optional.empty(); } @Nullable @Override public Optional get(WebContext context, String key) { - final Cookie cookie = ContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key); + final Cookie cookie = WebContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key); Object value = null; if (cookie != null) { value = uncompressDecryptBase64(cookie.getValue()); @@ -112,7 +112,7 @@ public void set(WebContext context, String key, @Nullable Object value) cookie.setDomain(""); cookie.setHttpOnly(true); - cookie.setSecure(ContextHelper.isHttpsOrSecure(context)); + cookie.setSecure(WebContextHelper.isHttpsOrSecure(context)); cookie.setPath("/"); cookie.setMaxAge(900); @@ -126,7 +126,7 @@ private String compressEncryptBase64(final Object o) || (o instanceof Map && ((Map) o).isEmpty())) { return null; } else { - byte[] bytes = javaSerializationHelper.serializeToBytes((Serializable) o); + byte[] bytes = javaSerializer.serializeToBytes(o); bytes = compress(bytes); if (bytes.length > 3000) { @@ -143,7 +143,7 @@ private Serializable uncompressDecryptBase64(final String v) if (v != null && !v.isEmpty()) { byte[] bytes = StringUtils.decodeBase64String(v); if (bytes != null) { - return javaSerializationHelper.deserializeFromBytes(unCompress(cryptoService.decrypt(bytes))); + return (Serializable) javaSerializer.deserializeFromBytes(unCompress(cryptoService.decrypt(bytes))); } } return null; @@ -187,7 +187,7 @@ private Object clearUserProfile(final Object value) } @Override - public Optional> buildFromTrackableSession(WebContext arg0, Object arg1) + public Optional buildFromTrackableSession(WebContext arg0, Object arg1) { return Optional.empty(); } From 8d2c5ad8a7c56a22e7638c6eab8c3ddde7031f80 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Fri, 7 Jun 2024 08:50:50 +0530 Subject: [PATCH 02/15] Fix test --- .../java/org/apache/druid/security/pac4j/Pac4jFilterTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java index 0523c970178d..1d5c76b7f0e0 100644 --- a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java +++ b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -26,12 +26,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import org.pac4j.core.context.JEEContext; +import org.pac4j.jee.context.JEEContext; import org.pac4j.core.exception.http.ForbiddenAction; import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.HttpAction; import org.pac4j.core.exception.http.WithLocationAction; -import org.pac4j.core.http.adapter.JEEHttpActionAdapter; +import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; From 57731c8823fe471acf0eb2d120b2e3a9f1e8bf8b Mon Sep 17 00:00:00 2001 From: pagrawal Date: Fri, 7 Jun 2024 10:36:30 +0530 Subject: [PATCH 03/15] securityLogic fixes --- .../apache/druid/security/pac4j/Pac4jFilter.java | 13 ++----------- .../druid/security/pac4j/Pac4jFilterTest.java | 2 +- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java index 9464f6aa0830..2f3b1182ccb4 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java @@ -43,7 +43,6 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Collection; public class Pac4jFilter implements Filter { @@ -101,17 +100,9 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo UserProfile profile = (UserProfile) securityLogic.perform( context, sessionStore, - pac4jConfig, - (JEEContext ctx, Collection profiles, Object... parameters) -> { - if (profiles.isEmpty()) { - LOGGER.warn("No profiles found after OIDC auth."); - return null; - } else { - return profiles.iterator().next(); - } - }, + pac4jConfig, null, JEEHttpActionAdapter.INSTANCE, - null, "none", null, null); + null, "none", null); // Changed the Authorizer from null to "none". // In the older version, if it is null, it simply grant access and returns authorized. // But in the newer pac4j version, it uses CsrfAuthorizer as default, And because of this, It was returning 403 in API calls. diff --git a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java index 1d5c76b7f0e0..bd90693d52db 100644 --- a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java +++ b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -26,11 +26,11 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import org.pac4j.jee.context.JEEContext; import org.pac4j.core.exception.http.ForbiddenAction; import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.HttpAction; import org.pac4j.core.exception.http.WithLocationAction; +import org.pac4j.jee.context.JEEContext; import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; import javax.servlet.http.HttpServletRequest; From 62113ec8cba89e4f2f6bdac702fd3ca618af26c8 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Fri, 7 Jun 2024 11:34:18 +0530 Subject: [PATCH 04/15] Update JDK Version --- licenses.yaml | 2 +- owasp-dependency-check-suppressions.xml | 10 ---------- pom.xml | 2 +- 3 files changed, 2 insertions(+), 12 deletions(-) diff --git a/licenses.yaml b/licenses.yaml index 752e82081643..b5ecb9d5ea5a 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -788,7 +788,7 @@ name: pac4j-core java security library license_category: binary module: extensions/druid-pac4j license_name: Apache License version 2.0 -version: 4.5.7 +version: 5.7.3 libraries: - org.pac4j: pac4j-core diff --git a/owasp-dependency-check-suppressions.xml b/owasp-dependency-check-suppressions.xml index 41d71fbb24a4..f3171dd0fa0b 100644 --- a/owasp-dependency-check-suppressions.xml +++ b/owasp-dependency-check-suppressions.xml @@ -341,16 +341,6 @@ CVE-2022-25647 - - - - - - CVE-2021-44878 - - 1.8 1.8 - 8 + 11 UTF-8 0.9.0.M2 5.3.0 From ad2d4e2c9380038199dfe12d592e90be322318ca Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 18 Jun 2024 09:48:19 +0530 Subject: [PATCH 05/15] Revert changes in the existing extension --- extensions-core/druid-pac4j/pom.xml | 11 ++---- .../druid/security/pac4j/Pac4jFilter.java | 36 +++++++++++-------- .../security/pac4j/Pac4jSessionStore.java | 26 +++++++------- .../druid/security/pac4j/Pac4jFilterTest.java | 6 ++-- licenses.yaml | 4 +-- owasp-dependency-check-suppressions.xml | 10 ++++++ pom.xml | 2 +- 7 files changed, 54 insertions(+), 41 deletions(-) diff --git a/extensions-core/druid-pac4j/pom.xml b/extensions-core/druid-pac4j/pom.xml index 74be55054e52..0db6afdd70b7 100644 --- a/extensions-core/druid-pac4j/pom.xml +++ b/extensions-core/druid-pac4j/pom.xml @@ -34,12 +34,12 @@ - 5.7.3 + 4.5.7 1.7 - 9.37.2 - 10.1 + 8.22.1 + 8.22 @@ -127,11 +127,6 @@ org.pac4j pac4j-core - ${pac4j.version} - - - org.pac4j - pac4j-javaee ${pac4j.version} diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java index 2f3b1182ccb4..7b942685d0c0 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java @@ -24,14 +24,14 @@ import org.apache.druid.server.security.AuthConfig; import org.apache.druid.server.security.AuthenticationResult; import org.pac4j.core.config.Config; +import org.pac4j.core.context.JEEContext; import org.pac4j.core.context.session.SessionStore; import org.pac4j.core.engine.CallbackLogic; import org.pac4j.core.engine.DefaultCallbackLogic; import org.pac4j.core.engine.DefaultSecurityLogic; import org.pac4j.core.engine.SecurityLogic; +import org.pac4j.core.http.adapter.JEEHttpActionAdapter; import org.pac4j.core.profile.UserProfile; -import org.pac4j.jee.context.JEEContext; -import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; import javax.servlet.Filter; import javax.servlet.FilterChain; @@ -43,15 +43,16 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Collection; public class Pac4jFilter implements Filter { private static final Logger LOGGER = new Logger(Pac4jFilter.class); private final Config pac4jConfig; - private final SecurityLogic securityLogic; - private final CallbackLogic callbackLogic; - private final SessionStore sessionStore; + private final SecurityLogic securityLogic; + private final CallbackLogic callbackLogic; + private final SessionStore sessionStore; private final String name; private final String authorizerName; @@ -59,8 +60,8 @@ public class Pac4jFilter implements Filter public Pac4jFilter(String name, String authorizerName, Config pac4jConfig, String cookiePassphrase) { this.pac4jConfig = pac4jConfig; - this.securityLogic = new DefaultSecurityLogic(); - this.callbackLogic = new DefaultCallbackLogic(); + this.securityLogic = new DefaultSecurityLogic<>(); + this.callbackLogic = new DefaultCallbackLogic<>(); this.name = name; this.authorizerName = authorizerName; @@ -87,22 +88,29 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; - JEEContext context = new JEEContext(httpServletRequest, httpServletResponse); + JEEContext context = new JEEContext(httpServletRequest, httpServletResponse, sessionStore); if (Pac4jCallbackResource.SELF_URL.equals(httpServletRequest.getRequestURI())) { callbackLogic.perform( context, - sessionStore, pac4jConfig, JEEHttpActionAdapter.INSTANCE, - "/", true, null); + "/", + true, false, false, null); } else { UserProfile profile = (UserProfile) securityLogic.perform( context, - sessionStore, - pac4jConfig, null, + pac4jConfig, + (JEEContext ctx, Collection profiles, Object... parameters) -> { + if (profiles.isEmpty()) { + LOGGER.warn("No profiles found after OIDC auth."); + return null; + } else { + return profiles.iterator().next(); + } + }, JEEHttpActionAdapter.INSTANCE, - null, "none", null); + null, "none", null, null); // Changed the Authorizer from null to "none". // In the older version, if it is null, it simply grant access and returns authorized. // But in the newer pac4j version, it uses CsrfAuthorizer as default, And because of this, It was returning 403 in API calls. @@ -118,4 +126,4 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo public void destroy() { } -} +} \ No newline at end of file diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java index 9a9d71ef440d..4cad5b5da9f4 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java @@ -23,14 +23,14 @@ import org.apache.druid.crypto.CryptoService; import org.apache.druid.java.util.common.StringUtils; import org.apache.druid.java.util.common.logger.Logger; +import org.pac4j.core.context.ContextHelper; import org.pac4j.core.context.Cookie; import org.pac4j.core.context.WebContext; -import org.pac4j.core.context.WebContextHelper; import org.pac4j.core.context.session.SessionStore; import org.pac4j.core.exception.TechnicalException; import org.pac4j.core.profile.CommonProfile; +import org.pac4j.core.util.JavaSerializationHelper; import org.pac4j.core.util.Pac4jConstants; -import org.pac4j.core.util.serializer.JavaSerializer; import javax.annotation.Nullable; import java.io.ByteArrayInputStream; @@ -46,19 +46,19 @@ * Code here is slight adaptation from KnoxSessionStore * for storing oauth session information in cookies. */ -public class Pac4jSessionStore implements SessionStore +public class Pac4jSessionStore implements SessionStore { private static final Logger LOGGER = new Logger(Pac4jSessionStore.class); public static final String PAC4J_SESSION_PREFIX = "pac4j.session."; - private final JavaSerializer javaSerializer; + private final JavaSerializationHelper javaSerializationHelper; private final CryptoService cryptoService; public Pac4jSessionStore(String cookiePassphrase) { - javaSerializer = new JavaSerializer(); + javaSerializationHelper = new JavaSerializationHelper(); cryptoService = new CryptoService( cookiePassphrase, "AES", @@ -72,16 +72,16 @@ public Pac4jSessionStore(String cookiePassphrase) } @Override - public Optional getSessionId(WebContext webContext, boolean b) + public String getOrCreateSessionId(WebContext context) { - return Optional.empty(); + return null; } @Nullable @Override public Optional get(WebContext context, String key) { - final Cookie cookie = WebContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key); + final Cookie cookie = ContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key); Object value = null; if (cookie != null) { value = uncompressDecryptBase64(cookie.getValue()); @@ -112,7 +112,7 @@ public void set(WebContext context, String key, @Nullable Object value) cookie.setDomain(""); cookie.setHttpOnly(true); - cookie.setSecure(WebContextHelper.isHttpsOrSecure(context)); + cookie.setSecure(ContextHelper.isHttpsOrSecure(context)); cookie.setPath("/"); cookie.setMaxAge(900); @@ -126,7 +126,7 @@ private String compressEncryptBase64(final Object o) || (o instanceof Map && ((Map) o).isEmpty())) { return null; } else { - byte[] bytes = javaSerializer.serializeToBytes(o); + byte[] bytes = javaSerializationHelper.serializeToBytes((Serializable) o); bytes = compress(bytes); if (bytes.length > 3000) { @@ -143,7 +143,7 @@ private Serializable uncompressDecryptBase64(final String v) if (v != null && !v.isEmpty()) { byte[] bytes = StringUtils.decodeBase64String(v); if (bytes != null) { - return (Serializable) javaSerializer.deserializeFromBytes(unCompress(cryptoService.decrypt(bytes))); + return javaSerializationHelper.deserializeFromBytes(unCompress(cryptoService.decrypt(bytes))); } } return null; @@ -187,7 +187,7 @@ private Object clearUserProfile(final Object value) } @Override - public Optional buildFromTrackableSession(WebContext arg0, Object arg1) + public Optional> buildFromTrackableSession(WebContext arg0, Object arg1) { return Optional.empty(); } @@ -209,4 +209,4 @@ public boolean renewSession(final WebContext context) { return false; } -} +} \ No newline at end of file diff --git a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java index bd90693d52db..0cea57ab10d0 100644 --- a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java +++ b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -26,12 +26,12 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.pac4j.core.context.JEEContext; import org.pac4j.core.exception.http.ForbiddenAction; import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.HttpAction; import org.pac4j.core.exception.http.WithLocationAction; -import org.pac4j.jee.context.JEEContext; -import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; +import org.pac4j.core.http.adapter.JEEHttpActionAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -74,4 +74,4 @@ public void testActionAdapterForForbidden() Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_FORBIDDEN); } -} +} \ No newline at end of file diff --git a/licenses.yaml b/licenses.yaml index b5ecb9d5ea5a..42525bd34f4d 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -788,7 +788,7 @@ name: pac4j-core java security library license_category: binary module: extensions/druid-pac4j license_name: Apache License version 2.0 -version: 5.7.3 +version: 4.5.7 libraries: - org.pac4j: pac4j-core @@ -829,7 +829,7 @@ name: com.nimbusds oauth2-oidc-sdk license_category: binary module: extensions/druid-pac4j license_name: Apache License version 2.0 -version: 8.22 +version: 9.37.2 libraries: - com.nimbusds: oauth2-oidc-sdk diff --git a/owasp-dependency-check-suppressions.xml b/owasp-dependency-check-suppressions.xml index f3171dd0fa0b..41d71fbb24a4 100644 --- a/owasp-dependency-check-suppressions.xml +++ b/owasp-dependency-check-suppressions.xml @@ -341,6 +341,16 @@ CVE-2022-25647 + + + + + + CVE-2021-44878 + + 1.8 1.8 - 11 + 8 UTF-8 0.9.0.M2 5.3.0 From 46359fe27c9bd99cda516fbd107453186368bad0 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 18 Jun 2024 10:52:43 +0530 Subject: [PATCH 06/15] Add extension druid-pac4j-v5 --- distribution/pom.xml | 2 + docs/configuration/extensions.md | 60 ++--- .../extensions-core/druid-pac4j-v5.md | 57 +++++ extensions-core/druid-pac4j-v5/pom.xml | 175 +++++++++++++++ .../pac4j/CustomSSLResourceRetriever.java | 60 +++++ .../druid/security/pac4j/JwtAuthFilter.java | 129 +++++++++++ .../security/pac4j/JwtAuthenticator.java | 114 ++++++++++ .../druid/security/pac4j/OIDCConfig.java | 92 ++++++++ .../security/pac4j/Pac4jAuthenticator.java | 154 +++++++++++++ .../security/pac4j/Pac4jCallbackResource.java | 57 +++++ .../security/pac4j/Pac4jCommonConfig.java | 68 ++++++ .../security/pac4j/Pac4jDruidModule.java | 53 +++++ .../druid/security/pac4j/Pac4jFilter.java | 121 ++++++++++ .../security/pac4j/Pac4jSessionStore.java | 212 ++++++++++++++++++ ...rg.apache.druid.initialization.DruidModule | 16 ++ .../security/pac4j/JwtAuthenticatorTest.java | 186 +++++++++++++++ .../druid/security/pac4j/OIDCConfigTest.java | 75 +++++++ .../security/pac4j/Pac4jCommonConfigTest.java | 49 ++++ .../druid/security/pac4j/Pac4jFilterTest.java | 77 +++++++ .../security/pac4j/Pac4jSessionStoreTest.java | 134 +++++++++++ .../druid/security/pac4j/Pac4jFilter.java | 2 +- .../security/pac4j/Pac4jSessionStore.java | 2 +- .../druid/security/pac4j/Pac4jFilterTest.java | 2 +- integration-tests-ex/docs/docker.md | 1 + licenses.yaml | 47 ++++ pom.xml | 1 + website/.spelling | 1 + 27 files changed, 1915 insertions(+), 32 deletions(-) create mode 100644 docs/development/extensions-core/druid-pac4j-v5.md create mode 100644 extensions-core/druid-pac4j-v5/pom.xml create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java create mode 100644 extensions-core/druid-pac4j-v5/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule create mode 100644 extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/JwtAuthenticatorTest.java create mode 100644 extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java create mode 100644 extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java create mode 100644 extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java create mode 100644 extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jSessionStoreTest.java diff --git a/distribution/pom.xml b/distribution/pom.xml index 136935433e93..18c1da344a97 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -252,6 +252,8 @@ -c org.apache.druid.extensions:druid-pac4j -c + org.apache.druid.extensions:druid-pac4j-v5 + -c org.apache.druid.extensions:druid-ranger-security -c org.apache.druid.extensions:druid-kubernetes-extensions diff --git a/docs/configuration/extensions.md b/docs/configuration/extensions.md index 29356ebc05cc..e50bfbb12240 100644 --- a/docs/configuration/extensions.md +++ b/docs/configuration/extensions.md @@ -34,35 +34,37 @@ metadata store. Many clusters will also use additional extensions. Core extensions are maintained by Druid committers. -|Name|Description|Docs| -|----|-----------|----| -|druid-avro-extensions|Support for data in Apache Avro data format.|[link](../development/extensions-core/avro.md)| -|druid-azure-extensions|Microsoft Azure deep storage.|[link](../development/extensions-core/azure.md)| -|druid-basic-security|Support for Basic HTTP authentication and role-based access control.|[link](../development/extensions-core/druid-basic-security.md)| -|druid-bloom-filter|Support for providing Bloom filters in druid queries.|[link](../development/extensions-core/bloom-filter.md)| -|druid-datasketches|Support for approximate counts and set operations with [Apache DataSketches](https://datasketches.apache.org/).|[link](../development/extensions-core/datasketches-extension.md)| -|druid-google-extensions|Google Cloud Storage deep storage.|[link](../development/extensions-core/google.md)| -|druid-hdfs-storage|HDFS deep storage.|[link](../development/extensions-core/hdfs.md)| -|druid-histogram|Approximate histograms and quantiles aggregator. Deprecated, please use the [DataSketches quantiles aggregator](../development/extensions-core/datasketches-quantiles.md) from the `druid-datasketches` extension instead.|[link](../development/extensions-core/approximate-histograms.md)| -|druid-kafka-extraction-namespace|Apache Kafka-based namespaced lookup. Requires namespace lookup extension.|[link](../querying/kafka-extraction-namespace.md)| -|druid-kafka-indexing-service|Supervised exactly-once Apache Kafka ingestion for the indexing service.|[link](../ingestion/kafka-ingestion.md)| -|druid-kinesis-indexing-service|Supervised exactly-once Kinesis ingestion for the indexing service.|[link](../ingestion/kinesis-ingestion.md)| -|druid-kerberos|Kerberos authentication for druid processes.|[link](../development/extensions-core/druid-kerberos.md)| -|druid-lookups-cached-global|A module for [lookups](../querying/lookups.md) providing a jvm-global eager caching for lookups. It provides JDBC and URI implementations for fetching lookup data.|[link](../querying/lookups-cached-global.md)| -|druid-lookups-cached-single| Per lookup caching module to support the use cases where a lookup need to be isolated from the global pool of lookups |[link](../development/extensions-core/druid-lookups.md)| -|druid-multi-stage-query| Support for the multi-stage query architecture for Apache Druid and the multi-stage query task engine.|[link](../multi-stage-query/index.md)| -|druid-orc-extensions|Support for data in Apache ORC data format.|[link](../development/extensions-core/orc.md)| -|druid-parquet-extensions|Support for data in Apache Parquet data format. Requires druid-avro-extensions to be loaded.|[link](../development/extensions-core/parquet.md)| -|druid-protobuf-extensions| Support for data in Protobuf data format.|[link](../development/extensions-core/protobuf.md)| -|druid-ranger-security|Support for access control through Apache Ranger.|[link](../development/extensions-core/druid-ranger-security.md)| -|druid-s3-extensions|Interfacing with data in AWS S3, and using S3 as deep storage.|[link](../development/extensions-core/s3.md)| -|druid-ec2-extensions|Interfacing with AWS EC2 for autoscaling middle managers|UNDOCUMENTED| -|druid-aws-rds-extensions|Support for AWS token based access to AWS RDS DB Cluster.|[link](../development/extensions-core/druid-aws-rds.md)| -|druid-stats|Statistics related module including variance and standard deviation.|[link](../development/extensions-core/stats.md)| -|mysql-metadata-storage|MySQL metadata store.|[link](../development/extensions-core/mysql.md)| -|postgresql-metadata-storage|PostgreSQL metadata store.|[link](../development/extensions-core/postgresql.md)| -|simple-client-sslcontext|Simple SSLContext provider module to be used by Druid's internal HttpClient when talking to other Druid processes over HTTPS.|[link](../development/extensions-core/simple-client-sslcontext.md)| -|druid-pac4j|OpenID Connect authentication for druid processes.|[link](../development/extensions-core/druid-pac4j.md)| +| Name | Description | Docs | +|----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------| +| druid-avro-extensions | Support for data in Apache Avro data format. | [link](../development/extensions-core/avro.md) | +| druid-azure-extensions | Microsoft Azure deep storage. | [link](../development/extensions-core/azure.md) | +| druid-basic-security | Support for Basic HTTP authentication and role-based access control. | [link](../development/extensions-core/druid-basic-security.md) | +| druid-bloom-filter | Support for providing Bloom filters in druid queries. | [link](../development/extensions-core/bloom-filter.md) | +| druid-datasketches | Support for approximate counts and set operations with [Apache DataSketches](https://datasketches.apache.org/). | [link](../development/extensions-core/datasketches-extension.md) | +| druid-google-extensions | Google Cloud Storage deep storage. | [link](../development/extensions-core/google.md) | +| druid-hdfs-storage | HDFS deep storage. | [link](../development/extensions-core/hdfs.md) | +| druid-histogram | Approximate histograms and quantiles aggregator. Deprecated, please use the [DataSketches quantiles aggregator](../development/extensions-core/datasketches-quantiles.md) from the `druid-datasketches` extension instead. | [link](../development/extensions-core/approximate-histograms.md) | +| druid-kafka-extraction-namespace | Apache Kafka-based namespaced lookup. Requires namespace lookup extension. | [link](../querying/kafka-extraction-namespace.md) | +| druid-kafka-indexing-service | Supervised exactly-once Apache Kafka ingestion for the indexing service. | [link](../ingestion/kafka-ingestion.md) | +| druid-kinesis-indexing-service | Supervised exactly-once Kinesis ingestion for the indexing service. | [link](../ingestion/kinesis-ingestion.md) | +| druid-kerberos | Kerberos authentication for druid processes. | [link](../development/extensions-core/druid-kerberos.md) | +| druid-lookups-cached-global | A module for [lookups](../querying/lookups.md) providing a jvm-global eager caching for lookups. It provides JDBC and URI implementations for fetching lookup data. | [link](../querying/lookups-cached-global.md) | +| druid-lookups-cached-single | Per lookup caching module to support the use cases where a lookup need to be isolated from the global pool of lookups | [link](../development/extensions-core/druid-lookups.md) | +| druid-multi-stage-query | Support for the multi-stage query architecture for Apache Druid and the multi-stage query task engine. | [link](../multi-stage-query/index.md) | +| druid-orc-extensions | Support for data in Apache ORC data format. | [link](../development/extensions-core/orc.md) | +| druid-parquet-extensions | Support for data in Apache Parquet data format. Requires druid-avro-extensions to be loaded. | [link](../development/extensions-core/parquet.md) | +| druid-protobuf-extensions | Support for data in Protobuf data format. | [link](../development/extensions-core/protobuf.md) | +| druid-ranger-security | Support for access control through Apache Ranger. | [link](../development/extensions-core/druid-ranger-security.md) | +| druid-s3-extensions | Interfacing with data in AWS S3, and using S3 as deep storage. | [link](../development/extensions-core/s3.md) | +| druid-ec2-extensions | Interfacing with AWS EC2 for autoscaling middle managers | UNDOCUMENTED | +| druid-aws-rds-extensions | Support for AWS token based access to AWS RDS DB Cluster. | [link](../development/extensions-core/druid-aws-rds.md) | +| druid-stats | Statistics related module including variance and standard deviation. | [link](../development/extensions-core/stats.md) | +| mysql-metadata-storage | MySQL metadata store. | [link](../development/extensions-core/mysql.md) | +| postgresql-metadata-storage | PostgreSQL metadata store. | [link](../development/extensions-core/postgresql.md) | +| simple-client-sslcontext | Simple SSLContext provider module to be used by Druid's internal HttpClient when talking to other Druid processes over HTTPS. | [link](../development/extensions-core/simple-client-sslcontext.md) | +| druid-pac4j | OpenID Connect authentication for druid processes. | [link](../development/extensions-core/druid-pac4j.md) | +| druid-pac4j-v5 | OpenID Connect authentication for druid processes. Uses v5+ for pac4j. Incompatible with JDK8. | [link](../development/extensions-core/druid-pac4j-v5.md) | + |druid-kubernetes-extensions|Druid cluster deployment on Kubernetes without Zookeeper.|[link](../development/extensions-core/kubernetes.md)| ## Community extensions diff --git a/docs/development/extensions-core/druid-pac4j-v5.md b/docs/development/extensions-core/druid-pac4j-v5.md new file mode 100644 index 000000000000..65e75611587e --- /dev/null +++ b/docs/development/extensions-core/druid-pac4j-v5.md @@ -0,0 +1,57 @@ +--- +id: druid-pac4j-v5 +title: "Druid pac4j based Security extension which uses pac4j v5+" +--- + + + + +Apache Druid Extension to enable [OpenID Connect](https://openid.net/connect/) based Authentication for Druid Processes using [pac4j](https://github.com/pac4j/pac4j) as the underlying client library. +This can be used with any authentication server that supports same e.g. [Okta](https://developer.okta.com/). +The pac4j authenticator should only be used at the router node to enable a group of users in existing authentication server to interact with Druid cluster, using the [web console](../../operations/web-console.md). + +This extension also provides a JWT authenticator that validates [ID Tokens](https://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken) associated with a request. ID Tokens are attached to the request under the `Authorization` header with the bearer token prefix - `Bearer `. This authenticator is intended for services to talk to Druid by initially authenticating with an OIDC server to retrieve the ID Token which is then attached to every Druid request. + +This extension does not support JDBC client authentication. + +## Configuration + +### Creating an Authenticator +``` +#Create a pac4j web user authenticator +druid.auth.authenticatorChain=["pac4j"] +druid.auth.authenticator.pac4j.type=pac4j + +#Create a JWT token authenticator +druid.auth.authenticatorChain=["jwt"] +druid.auth.authenticator.jwt.type=jwt +``` + +### Properties +|Property|Description|Default|required| +|--------|---------------|-----------|-------| +|`druid.auth.pac4j.cookiePassphrase`|passphrase for encrypting the cookies used to manage authentication session with browser. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes| +|`druid.auth.pac4j.readTimeout`|Socket connect and read timeout duration used when communicating with authentication server|PT5S|No| +|`druid.auth.pac4j.enableCustomSslContext`|Whether to use custom SSLContext setup via [simple-client-sslcontext](simple-client-sslcontext.md) extension which must be added to extensions list when this property is set to true.|false|No| +|`druid.auth.pac4j.oidc.clientID`|OAuth Client Application id.|none|Yes| +|`druid.auth.pac4j.oidc.clientSecret`|OAuth Client Application secret. It can be provided as plaintext string or The [Password Provider](../../operations/password-provider.md).|none|Yes| +|`druid.auth.pac4j.oidc.discoveryURI`|discovery URI for fetching OP metadata [see this](http://openid.net/specs/openid-connect-discovery-1_0.html).|none|Yes| +|`druid.auth.pac4j.oidc.oidcClaim`|[claim](https://openid.net/specs/openid-connect-core-1_0.html#Claims) that will be extracted from the ID Token after validation.|name|No| +|`druid.auth.pac4j.oidc.scope`| scope is used by an application during authentication to authorize access to a user's details |`openid profile email`|No| diff --git a/extensions-core/druid-pac4j-v5/pom.xml b/extensions-core/druid-pac4j-v5/pom.xml new file mode 100644 index 000000000000..3c6f58867608 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/pom.xml @@ -0,0 +1,175 @@ + + + + + 4.0.0 + + org.apache.druid.extensions + druid-pac4j-v5 + druid-pac4j-v5 + druid-pac4j-v5 + + + org.apache.druid + druid + 31.0.0-SNAPSHOT + ../../pom.xml + + + + 5.7.3 + + + 1.7 + 9.37.2 + 10.1 + + + + + org.apache.druid + druid-server + ${project.parent.version} + provided + + + joda-time + joda-time + provided + + + org.apache.druid + druid-processing + ${project.parent.version} + provided + + + org.pac4j + pac4j-oidc + ${pac4j.version} + + + + org.mockito + mockito-core + + + + + + com.nimbusds + lang-tag + ${nimbus.lang.tag.version} + + + com.nimbusds + nimbus-jose-jwt + ${nimbus.jose.jwt.version} + + + com.nimbusds + oauth2-oidc-sdk + ${oauth2.oidc.sdk.version} + + + + com.google.code.findbugs + jsr305 + provided + + + com.google.guava + guava + provided + + + com.fasterxml.jackson.core + jackson-annotations + provided + + + com.google.inject + guice + provided + + + javax.servlet + javax.servlet-api + provided + + + commons-io + commons-io + provided + + + com.fasterxml.jackson.core + jackson-databind + provided + + + org.pac4j + pac4j-core + ${pac4j.version} + + + org.pac4j + pac4j-javaee + ${pac4j.version} + + + javax.ws.rs + jsr311-api + provided + + + + junit + junit + test + + + org.easymock + easymock + test + + + org.mockito + mockito-core + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + + com.nimbusds:lang-tag + + + + + + + diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java new file mode 100644 index 000000000000..e3ba9eeda34b --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/CustomSSLResourceRetriever.java @@ -0,0 +1,60 @@ +/* + * 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.druid.security.pac4j; + + +import com.google.common.primitives.Ints; +import com.nimbusds.jose.util.DefaultResourceRetriever; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocketFactory; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URL; + + +/** + * This class exists only to enable use of custom SSLSocketFactory on top of builtin class. This could be removed + * when same functionality has been added to original class com.nimbusds.jose.util.DefaultResourceRetriever. + */ +public class CustomSSLResourceRetriever extends DefaultResourceRetriever +{ + private SSLSocketFactory sslSocketFactory; + + public CustomSSLResourceRetriever(long readTimeout, SSLSocketFactory sslSocketFactory) + { + // super(..) has to be the very first statement in constructor. + super(Ints.checkedCast(readTimeout), Ints.checkedCast(readTimeout)); + + this.sslSocketFactory = sslSocketFactory; + } + + @Override + protected HttpURLConnection openConnection(final URL url) throws IOException + { + HttpURLConnection con = super.openConnection(url); + + if (sslSocketFactory != null && con instanceof HttpsURLConnection) { + ((HttpsURLConnection) con).setSSLSocketFactory(sslSocketFactory); + } + + return con; + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java new file mode 100644 index 000000000000..a0132615ff5f --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthFilter.java @@ -0,0 +1,129 @@ +/* + * 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.druid.security.pac4j; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.jwt.JWTParser; +import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; +import org.apache.druid.java.util.common.logger.Logger; +import org.apache.druid.server.security.AuthConfig; +import org.apache.druid.server.security.AuthenticationResult; +import org.pac4j.core.context.HttpConstants; +import org.pac4j.oidc.profile.creator.TokenValidator; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.text.ParseException; +import java.util.Optional; + +public class JwtAuthFilter implements Filter +{ + private static final Logger LOG = new Logger(JwtAuthFilter.class); + + private final String authorizerName; + private final String name; + private final OIDCConfig oidcConfig; + private final TokenValidator tokenValidator; + + public JwtAuthFilter(String authorizerName, String name, OIDCConfig oidcConfig, TokenValidator tokenValidator) + { + this.authorizerName = authorizerName; + this.name = name; + this.oidcConfig = oidcConfig; + this.tokenValidator = tokenValidator; + } + + @Override + public void init(FilterConfig filterConfig) + { + + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException + { + // Skip this filter if the request has already been authenticated + if (servletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT) != null) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; + Optional idToken = extractBearerToken(httpServletRequest); + + if (idToken.isPresent()) { + try { + // Parses the JWT and performs the ID Token validation specified in the OpenID spec: https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation + IDTokenClaimsSet claims = tokenValidator.validate(JWTParser.parse(idToken.get()), null); + if (claims != null) { + Optional claim = Optional.ofNullable(claims.getStringClaim(oidcConfig.getOidcClaim())); + + if (claim.isPresent()) { + LOG.debug("Authentication successful for " + oidcConfig.getClientID()); + AuthenticationResult authenticationResult = new AuthenticationResult( + claim.get(), + authorizerName, + name, + null + ); + servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult); + } else { + LOG.error( + "Authentication failed! Please ensure that the ID token is valid and it contains the configured claim."); + httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + } + } + catch (BadJOSEException | JOSEException | ParseException e) { + LOG.error(e, "Failed to parse JWT token"); + } + } + filterChain.doFilter(servletRequest, servletResponse); + } + + + @Override + public void destroy() + { + + } + + private static Optional extractBearerToken(HttpServletRequest request) + { + String header = request.getHeader(HttpConstants.AUTHORIZATION_HEADER); + if (header == null || !header.startsWith(HttpConstants.BEARER_HEADER_PREFIX)) { + LOG.debug("Request does not contain bearer authentication scheme"); + return Optional.empty(); + } + String headerWithoutPrefix = header.substring(HttpConstants.BEARER_HEADER_PREFIX.length()); + return Optional.of(headerWithoutPrefix); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java new file mode 100644 index 000000000000..96c82a491a1b --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/JwtAuthenticator.java @@ -0,0 +1,114 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import org.apache.druid.server.security.AuthenticationResult; +import org.apache.druid.server.security.Authenticator; +import org.pac4j.oidc.config.OidcConfiguration; +import org.pac4j.oidc.profile.creator.TokenValidator; + +import javax.annotation.Nullable; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import java.util.EnumSet; +import java.util.Map; + +@JsonTypeName("jwt") +public class JwtAuthenticator implements Authenticator +{ + private final String authorizerName; + private final OIDCConfig oidcConfig; + private final Supplier tokenValidatorSupplier; + private final String name; + + @JsonCreator + public JwtAuthenticator( + @JsonProperty("name") String name, + @JsonProperty("authorizerName") String authorizerName, + @JacksonInject OIDCConfig oidcConfig + ) + { + this.name = name; + this.oidcConfig = oidcConfig; + this.authorizerName = authorizerName; + + this.tokenValidatorSupplier = Suppliers.memoize(() -> createTokenValidator(oidcConfig)); + } + + @Override + public Filter getFilter() + { + return new JwtAuthFilter(authorizerName, name, oidcConfig, tokenValidatorSupplier.get()); + } + + @Override + public Class getFilterClass() + { + return JwtAuthFilter.class; + } + + @Override + public Map getInitParameters() + { + return null; + } + + @Override + public String getPath() + { + return "/*"; + } + + @Nullable + @Override + public EnumSet getDispatcherType() + { + return null; + } + + @Nullable + @Override + public String getAuthChallengeHeader() + { + return null; + } + + @Nullable + @Override + public AuthenticationResult authenticateJDBCContext(Map context) + { + return null; + } + + private TokenValidator createTokenValidator(OIDCConfig config) + { + OidcConfiguration oidcConfiguration = new OidcConfiguration(); + oidcConfiguration.setClientId(config.getClientID()); + oidcConfiguration.setSecret(config.getClientSecret().getPassword()); + oidcConfiguration.setDiscoveryURI(config.getDiscoveryURI()); + return new TokenValidator(oidcConfiguration); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java new file mode 100644 index 000000000000..376181416556 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/OIDCConfig.java @@ -0,0 +1,92 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import org.apache.druid.metadata.PasswordProvider; + +import javax.annotation.Nullable; + +public class OIDCConfig +{ + private final String DEFAULT_SCOPE = "name"; + @JsonProperty + private final String clientID; + + @JsonProperty + private final PasswordProvider clientSecret; + + @JsonProperty + private final String discoveryURI; + + @JsonProperty + private final String oidcClaim; + + @JsonProperty + private final String scope; + + @JsonCreator + public OIDCConfig( + @JsonProperty("clientID") String clientID, + @JsonProperty("clientSecret") PasswordProvider clientSecret, + @JsonProperty("discoveryURI") String discoveryURI, + @JsonProperty("oidcClaim") String oidcClaim, + @JsonProperty("scope") @Nullable String scope + ) + { + this.clientID = Preconditions.checkNotNull(clientID, "null clientID"); + this.clientSecret = Preconditions.checkNotNull(clientSecret, "null clientSecret"); + this.discoveryURI = Preconditions.checkNotNull(discoveryURI, "null discoveryURI"); + this.oidcClaim = oidcClaim == null ? DEFAULT_SCOPE : oidcClaim; + this.scope = scope; + } + + @JsonProperty + public String getClientID() + { + return clientID; + } + + @JsonProperty + public PasswordProvider getClientSecret() + { + return clientSecret; + } + + @JsonProperty + public String getDiscoveryURI() + { + return discoveryURI; + } + + @JsonProperty + public String getOidcClaim() + { + return oidcClaim; + } + + @JsonProperty + public String getScope() + { + return scope; + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java new file mode 100644 index 000000000000..b63fcdf72775 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jAuthenticator.java @@ -0,0 +1,154 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.primitives.Ints; +import com.google.inject.Provider; +import com.nimbusds.oauth2.sdk.http.HTTPRequest; +import org.apache.druid.server.security.AuthenticationResult; +import org.apache.druid.server.security.Authenticator; +import org.pac4j.core.config.Config; +import org.pac4j.core.http.callback.NoParameterCallbackUrlResolver; +import org.pac4j.core.http.url.DefaultUrlResolver; +import org.pac4j.oidc.client.OidcClient; +import org.pac4j.oidc.config.OidcConfiguration; + +import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import java.util.EnumSet; +import java.util.Map; + +@JsonTypeName("pac4j") +public class Pac4jAuthenticator implements Authenticator +{ + private final String name; + private final String authorizerName; + private final Supplier pac4jConfigSupplier; + private final Pac4jCommonConfig pac4jCommonConfig; + private final SSLSocketFactory sslSocketFactory; + + @JsonCreator + public Pac4jAuthenticator( + @JsonProperty("name") String name, + @JsonProperty("authorizerName") String authorizerName, + @JacksonInject Pac4jCommonConfig pac4jCommonConfig, + @JacksonInject OIDCConfig oidcConfig, + @JacksonInject Provider sslContextSupplier + ) + { + this.name = name; + this.authorizerName = authorizerName; + this.pac4jCommonConfig = pac4jCommonConfig; + + if (pac4jCommonConfig.isEnableCustomSslContext()) { + this.sslSocketFactory = sslContextSupplier.get().getSocketFactory(); + } else { + this.sslSocketFactory = null; + } + + this.pac4jConfigSupplier = Suppliers.memoize(() -> createPac4jConfig(oidcConfig)); + } + + @Override + public Filter getFilter() + { + return new Pac4jFilter( + name, + authorizerName, + pac4jConfigSupplier.get(), + pac4jCommonConfig.getCookiePassphrase().getPassword() + ); + } + + @Override + public String getAuthChallengeHeader() + { + return null; + } + + @Override + @Nullable + public AuthenticationResult authenticateJDBCContext(Map context) + { + return null; + } + + + @Override + public Class getFilterClass() + { + return null; + } + + @Override + public Map getInitParameters() + { + return null; + } + + @Override + public String getPath() + { + return "/*"; + } + + @Override + public EnumSet getDispatcherType() + { + return null; + } + + private Config createPac4jConfig(OIDCConfig oidcConfig) + { + OidcConfiguration oidcConf = new OidcConfiguration(); + oidcConf.setClientId(oidcConfig.getClientID()); + oidcConf.setSecret(oidcConfig.getClientSecret().getPassword()); + oidcConf.setDiscoveryURI(oidcConfig.getDiscoveryURI()); + oidcConf.setScope(oidcConfig.getScope()); + oidcConf.setExpireSessionWithToken(true); + oidcConf.setUseNonce(true); + oidcConf.setReadTimeout(Ints.checkedCast(pac4jCommonConfig.getReadTimeout().getMillis())); + + oidcConf.setResourceRetriever( + // ResourceRetriever is used to get Auth server configuration from "discoveryURI" + new CustomSSLResourceRetriever(pac4jCommonConfig.getReadTimeout().getMillis(), sslSocketFactory) + ); + + OidcClient oidcClient = new OidcClient(oidcConf); + oidcClient.setUrlResolver(new DefaultUrlResolver(true)); + oidcClient.setCallbackUrlResolver(new NoParameterCallbackUrlResolver()); + + // This is used by OidcClient in various places to make HTTPrequests. + if (sslSocketFactory != null) { + HTTPRequest.setDefaultSSLSocketFactory(sslSocketFactory); + } + + return new Config(Pac4jCallbackResource.SELF_URL, oidcClient); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java new file mode 100644 index 000000000000..e975b7cfdd3c --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCallbackResource.java @@ -0,0 +1,57 @@ +/* + * 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.druid.security.pac4j; + +import com.google.inject.Inject; +import org.apache.druid.guice.LazySingleton; +import org.apache.druid.java.util.common.logger.Logger; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.core.Response; + +/** + * Fixed Callback endpoint used after successful login with Identity Provider e.g. OAuth server. + * See https://www.pac4j.org/blog/understanding-the-callback-endpoint.html + */ +@Path(Pac4jCallbackResource.SELF_URL) +@LazySingleton +public class Pac4jCallbackResource +{ + public static final String SELF_URL = "/druid-ext/druid-pac4j/callback"; + + private static final Logger LOGGER = new Logger(Pac4jCallbackResource.class); + + @Inject + public Pac4jCallbackResource() + { + } + + @GET + public Response callback() + { + LOGGER.error( + new RuntimeException(), + "This endpoint is to be handled by the pac4j filter to redirect users, request should never reach here." + ); + return Response.serverError().build(); + } +} + diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java new file mode 100644 index 000000000000..0a57869fb57e --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jCommonConfig.java @@ -0,0 +1,68 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Preconditions; +import org.apache.druid.metadata.PasswordProvider; +import org.joda.time.Duration; + +public class Pac4jCommonConfig +{ + @JsonProperty + private final boolean enableCustomSslContext; + + @JsonProperty + private final PasswordProvider cookiePassphrase; + + @JsonProperty + private final Duration readTimeout; + + @JsonCreator + public Pac4jCommonConfig( + @JsonProperty("enableCustomSslContext") boolean enableCustomSslContext, + @JsonProperty("cookiePassphrase") PasswordProvider cookiePassphrase, + @JsonProperty("readTimeout") Duration readTimeout + ) + { + this.enableCustomSslContext = enableCustomSslContext; + this.cookiePassphrase = Preconditions.checkNotNull(cookiePassphrase, "null cookiePassphrase"); + this.readTimeout = readTimeout == null ? Duration.millis(5000) : readTimeout; + } + + @JsonProperty + public boolean isEnableCustomSslContext() + { + return enableCustomSslContext; + } + + @JsonProperty + public PasswordProvider getCookiePassphrase() + { + return cookiePassphrase; + } + + @JsonProperty + public Duration getReadTimeout() + { + return readTimeout; + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java new file mode 100644 index 000000000000..f7113ab67a58 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jDruidModule.java @@ -0,0 +1,53 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.google.common.collect.ImmutableList; +import com.google.inject.Binder; +import org.apache.druid.guice.Jerseys; +import org.apache.druid.guice.JsonConfigProvider; +import org.apache.druid.initialization.DruidModule; + +import java.util.List; + +public class Pac4jDruidModule implements DruidModule +{ + @Override + public List getJacksonModules() + { + return ImmutableList.of( + new SimpleModule("Pac4jDruidSecurity").registerSubtypes( + Pac4jAuthenticator.class, + JwtAuthenticator.class + ) + ); + } + + @Override + public void configure(Binder binder) + { + JsonConfigProvider.bind(binder, "druid.auth.pac4j", Pac4jCommonConfig.class); + JsonConfigProvider.bind(binder, "druid.auth.pac4j.oidc", OIDCConfig.class); + + Jerseys.addResource(binder, Pac4jCallbackResource.class); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java new file mode 100644 index 000000000000..2f3b1182ccb4 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java @@ -0,0 +1,121 @@ +/* + * 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.druid.security.pac4j; + +import com.google.common.collect.ImmutableMap; +import org.apache.druid.java.util.common.logger.Logger; +import org.apache.druid.server.security.AuthConfig; +import org.apache.druid.server.security.AuthenticationResult; +import org.pac4j.core.config.Config; +import org.pac4j.core.context.session.SessionStore; +import org.pac4j.core.engine.CallbackLogic; +import org.pac4j.core.engine.DefaultCallbackLogic; +import org.pac4j.core.engine.DefaultSecurityLogic; +import org.pac4j.core.engine.SecurityLogic; +import org.pac4j.core.profile.UserProfile; +import org.pac4j.jee.context.JEEContext; +import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.io.IOException; + +public class Pac4jFilter implements Filter +{ + private static final Logger LOGGER = new Logger(Pac4jFilter.class); + + private final Config pac4jConfig; + private final SecurityLogic securityLogic; + private final CallbackLogic callbackLogic; + private final SessionStore sessionStore; + + private final String name; + private final String authorizerName; + + public Pac4jFilter(String name, String authorizerName, Config pac4jConfig, String cookiePassphrase) + { + this.pac4jConfig = pac4jConfig; + this.securityLogic = new DefaultSecurityLogic(); + this.callbackLogic = new DefaultCallbackLogic(); + + this.name = name; + this.authorizerName = authorizerName; + + this.sessionStore = new Pac4jSessionStore<>(cookiePassphrase); + } + + @Override + public void init(FilterConfig filterConfig) + { + } + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException + { + // If there's already an auth result, then we have authenticated already, skip this or else caller + // could get HTTP redirect even if one of the druid authenticators in chain has successfully authenticated. + if (servletRequest.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT) != null) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + + HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse; + JEEContext context = new JEEContext(httpServletRequest, httpServletResponse); + + if (Pac4jCallbackResource.SELF_URL.equals(httpServletRequest.getRequestURI())) { + callbackLogic.perform( + context, + sessionStore, + pac4jConfig, + JEEHttpActionAdapter.INSTANCE, + "/", true, null); + } else { + UserProfile profile = (UserProfile) securityLogic.perform( + context, + sessionStore, + pac4jConfig, null, + JEEHttpActionAdapter.INSTANCE, + null, "none", null); + // Changed the Authorizer from null to "none". + // In the older version, if it is null, it simply grant access and returns authorized. + // But in the newer pac4j version, it uses CsrfAuthorizer as default, And because of this, It was returning 403 in API calls. + if (profile != null && profile.getId() != null) { + AuthenticationResult authenticationResult = new AuthenticationResult(profile.getId(), authorizerName, name, ImmutableMap.of("profile", profile)); + servletRequest.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, authenticationResult); + filterChain.doFilter(servletRequest, servletResponse); + } + } + } + + @Override + public void destroy() + { + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java new file mode 100644 index 000000000000..9a9d71ef440d --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java @@ -0,0 +1,212 @@ +/* + * 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.druid.security.pac4j; + +import org.apache.commons.io.IOUtils; +import org.apache.druid.crypto.CryptoService; +import org.apache.druid.java.util.common.StringUtils; +import org.apache.druid.java.util.common.logger.Logger; +import org.pac4j.core.context.Cookie; +import org.pac4j.core.context.WebContext; +import org.pac4j.core.context.WebContextHelper; +import org.pac4j.core.context.session.SessionStore; +import org.pac4j.core.exception.TechnicalException; +import org.pac4j.core.profile.CommonProfile; +import org.pac4j.core.util.Pac4jConstants; +import org.pac4j.core.util.serializer.JavaSerializer; + +import javax.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import java.util.Optional; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +/** + * Code here is slight adaptation from KnoxSessionStore + * for storing oauth session information in cookies. + */ +public class Pac4jSessionStore implements SessionStore +{ + + private static final Logger LOGGER = new Logger(Pac4jSessionStore.class); + + public static final String PAC4J_SESSION_PREFIX = "pac4j.session."; + + private final JavaSerializer javaSerializer; + private final CryptoService cryptoService; + + public Pac4jSessionStore(String cookiePassphrase) + { + javaSerializer = new JavaSerializer(); + cryptoService = new CryptoService( + cookiePassphrase, + "AES", + "CBC", + "PKCS5Padding", + "PBKDF2WithHmacSHA256", + 128, + 65536, + 128 + ); + } + + @Override + public Optional getSessionId(WebContext webContext, boolean b) + { + return Optional.empty(); + } + + @Nullable + @Override + public Optional get(WebContext context, String key) + { + final Cookie cookie = WebContextHelper.getCookie(context, PAC4J_SESSION_PREFIX + key); + Object value = null; + if (cookie != null) { + value = uncompressDecryptBase64(cookie.getValue()); + } + LOGGER.debug("Get from session: [%s] = [%s]", key, value); + return Optional.ofNullable(value); + } + + @Override + public void set(WebContext context, String key, @Nullable Object value) + { + Object profile = value; + Cookie cookie; + + if (value == null) { + cookie = new Cookie(PAC4J_SESSION_PREFIX + key, null); + } else { + if (key.contentEquals(Pac4jConstants.USER_PROFILES)) { + /* trim the profile object */ + profile = clearUserProfile(value); + } + LOGGER.debug("Save in session: [%s] = [%s]", key, profile); + cookie = new Cookie( + PAC4J_SESSION_PREFIX + key, + compressEncryptBase64(profile) + ); + } + + cookie.setDomain(""); + cookie.setHttpOnly(true); + cookie.setSecure(WebContextHelper.isHttpsOrSecure(context)); + cookie.setPath("/"); + cookie.setMaxAge(900); + + context.addResponseCookie(cookie); + } + + @Nullable + private String compressEncryptBase64(final Object o) + { + if (o == null || "".equals(o) + || (o instanceof Map && ((Map) o).isEmpty())) { + return null; + } else { + byte[] bytes = javaSerializer.serializeToBytes(o); + + bytes = compress(bytes); + if (bytes.length > 3000) { + LOGGER.warn("Cookie too big, it might not be properly set"); + } + + return StringUtils.encodeBase64String(cryptoService.encrypt(bytes)); + } + } + + @Nullable + private Serializable uncompressDecryptBase64(final String v) + { + if (v != null && !v.isEmpty()) { + byte[] bytes = StringUtils.decodeBase64String(v); + if (bytes != null) { + return (Serializable) javaSerializer.deserializeFromBytes(unCompress(cryptoService.decrypt(bytes))); + } + } + return null; + } + + private byte[] compress(final byte[] data) + { + try (ByteArrayOutputStream byteStream = new ByteArrayOutputStream(data.length)) { + try (GZIPOutputStream gzip = new GZIPOutputStream(byteStream)) { + gzip.write(data); + } + return byteStream.toByteArray(); + } + catch (IOException ex) { + throw new TechnicalException(ex); + } + } + + private byte[] unCompress(final byte[] data) + { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(data); + GZIPInputStream gzip = new GZIPInputStream(inputStream)) { + return IOUtils.toByteArray(gzip); + } + catch (IOException ex) { + throw new TechnicalException(ex); + } + } + + private Object clearUserProfile(final Object value) + { + if (value instanceof Map) { + final Map profiles = (Map) value; + profiles.forEach((name, profile) -> profile.removeLoginData()); + return profiles; + } else { + final CommonProfile profile = (CommonProfile) value; + profile.removeLoginData(); + return profile; + } + } + + @Override + public Optional buildFromTrackableSession(WebContext arg0, Object arg1) + { + return Optional.empty(); + } + + @Override + public boolean destroySession(WebContext arg0) + { + return false; + } + + @Override + public Optional getTrackableSession(WebContext arg0) + { + return Optional.empty(); + } + + @Override + public boolean renewSession(final WebContext context) + { + return false; + } +} diff --git a/extensions-core/druid-pac4j-v5/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule b/extensions-core/druid-pac4j-v5/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule new file mode 100644 index 000000000000..e0a77f8254f3 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/main/resources/META-INF/services/org.apache.druid.initialization.DruidModule @@ -0,0 +1,16 @@ +# 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. + +org.apache.druid.security.pac4j.Pac4jDruidModule diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/JwtAuthenticatorTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/JwtAuthenticatorTest.java new file mode 100644 index 000000000000..73704731f2ab --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/JwtAuthenticatorTest.java @@ -0,0 +1,186 @@ +/* + * 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.druid.security.pac4j; + +import com.google.common.collect.ImmutableMap; +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.proc.BadJOSEException; +import com.nimbusds.oauth2.sdk.id.Issuer; +import com.nimbusds.oauth2.sdk.id.Subject; +import com.nimbusds.openid.connect.sdk.claims.IDTokenClaimsSet; +import org.apache.druid.server.security.AuthConfig; +import org.apache.druid.server.security.AuthenticationResult; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Test; +import org.pac4j.oidc.profile.creator.TokenValidator; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Collections; + +public class JwtAuthenticatorTest +{ + private static final String DUMMY_BEARER_TOKEN_HEADER = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + + @Test + public void testBearerToken() + throws IOException, ServletException + { + OIDCConfig configuration = EasyMock.createMock(OIDCConfig.class); + TokenValidator tokenValidator = EasyMock.createMock(TokenValidator.class); + + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(null); + EasyMock.expect(req.getHeader("Authorization")).andReturn("Nobearer"); + + EasyMock.replay(req); + + HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class); + EasyMock.replay(resp); + + FilterChain filterChain = EasyMock.createMock(FilterChain.class); + filterChain.doFilter(req, resp); + EasyMock.expectLastCall().times(1); + EasyMock.replay(filterChain); + + + JwtAuthenticator jwtAuthenticator = new JwtAuthenticator("jwt", "allowAll", configuration); + JwtAuthFilter authFilter = new JwtAuthFilter("allowAll", "jwt", configuration, tokenValidator); + authFilter.doFilter(req, resp, filterChain); + + EasyMock.verify(req, resp, filterChain); + Assert.assertEquals(jwtAuthenticator.getFilterClass(), JwtAuthFilter.class); + Assert.assertNull(jwtAuthenticator.getInitParameters()); + Assert.assertNull(jwtAuthenticator.authenticateJDBCContext(ImmutableMap.of())); + } + + @Test + public void testAuthenticatedRequest() throws ServletException, IOException + { + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn("AlreadyAuthenticated"); + + EasyMock.replay(req); + + HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class); + EasyMock.replay(resp); + + FilterChain filterChain = EasyMock.createMock(FilterChain.class); + filterChain.doFilter(req, resp); + EasyMock.expectLastCall().times(1); + EasyMock.replay(filterChain); + + JwtAuthFilter authFilter = new JwtAuthFilter("allowAll", "jwt", null, null); + authFilter.doFilter(req, resp, filterChain); + + EasyMock.verify(req, resp, filterChain); + } + + @Test + public void testValidClaim() + throws IOException, ServletException, BadJOSEException, JOSEException + { + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(null); + EasyMock.expect(req.getHeader("Authorization")).andReturn(DUMMY_BEARER_TOKEN_HEADER); + req.setAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT, new AuthenticationResult("foo", "allowAll", "jwt", null)); + EasyMock.expectLastCall().times(1); + EasyMock.replay(req); + + OIDCConfig configuration = EasyMock.createMock(OIDCConfig.class); + EasyMock.expect(configuration.getOidcClaim()).andReturn("iss"); + EasyMock.expect(configuration.getClientID()).andReturn("testClient"); + EasyMock.replay(configuration); + + TokenValidator tokenValidator = EasyMock.createMock(TokenValidator.class); + EasyMock.expect(tokenValidator.validate(EasyMock.anyObject(), EasyMock.anyObject())) + .andReturn(new IDTokenClaimsSet(new Issuer("foo"), + new Subject("testsub"), + Collections.emptyList(), + null, + null + )); + EasyMock.replay(tokenValidator); + + HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class); + EasyMock.replay(resp); + + FilterChain filterChain = EasyMock.createMock(FilterChain.class); + filterChain.doFilter(req, resp); + EasyMock.expectLastCall().times(1); + EasyMock.replay(filterChain); + + + JwtAuthenticator jwtAuthenticator = new JwtAuthenticator("jwt", "allowAll", configuration); + JwtAuthFilter authFilter = new JwtAuthFilter("allowAll", "jwt", configuration, tokenValidator); + authFilter.doFilter(req, resp, filterChain); + + EasyMock.verify(req, resp, filterChain); + Assert.assertEquals(jwtAuthenticator.getFilterClass(), JwtAuthFilter.class); + Assert.assertNull(jwtAuthenticator.getInitParameters()); + Assert.assertNull(jwtAuthenticator.authenticateJDBCContext(ImmutableMap.of())); + } + + @Test + public void testEmptyClaim() + throws IOException, ServletException, BadJOSEException, JOSEException + { + OIDCConfig configuration = EasyMock.createMock(OIDCConfig.class); + + HttpServletRequest req = EasyMock.createMock(HttpServletRequest.class); + EasyMock.expect(req.getAttribute(AuthConfig.DRUID_AUTHENTICATION_RESULT)).andReturn(null); + EasyMock.expect(req.getHeader("Authorization")).andReturn(DUMMY_BEARER_TOKEN_HEADER); + + EasyMock.replay(req); + + TokenValidator tokenValidator = EasyMock.createMock(TokenValidator.class); + // This doesn't return any claims for the default scope + EasyMock.expect(tokenValidator.validate(EasyMock.anyObject(), EasyMock.anyObject())) + .andReturn(new IDTokenClaimsSet(new Issuer("test"), + new Subject("testsub"), + Collections.emptyList(), + null, + null + )); + EasyMock.replay(tokenValidator); + + HttpServletResponse resp = EasyMock.createMock(HttpServletResponse.class); + resp.sendError(HttpServletResponse.SC_UNAUTHORIZED); + EasyMock.expectLastCall().times(1); + EasyMock.replay(resp); + + FilterChain filterChain = EasyMock.createMock(FilterChain.class); + EasyMock.replay(filterChain); + + + JwtAuthenticator jwtAuthenticator = new JwtAuthenticator("jwt", "allowAll", configuration); + JwtAuthFilter authFilter = new JwtAuthFilter("allowAll", "jwt", configuration, tokenValidator); + authFilter.doFilter(req, resp, filterChain); + + EasyMock.verify(req, resp, filterChain); + Assert.assertEquals(jwtAuthenticator.getFilterClass(), JwtAuthFilter.class); + Assert.assertNull(jwtAuthenticator.getInitParameters()); + Assert.assertNull(jwtAuthenticator.authenticateJDBCContext(ImmutableMap.of())); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java new file mode 100644 index 000000000000..c4192c020dfa --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/OIDCConfigTest.java @@ -0,0 +1,75 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +public class OIDCConfigTest +{ + @Test + public void testSerde() throws Exception + { + ObjectMapper jsonMapper = new ObjectMapper(); + + String jsonStr = "{\n" + + " \"clientID\": \"testid\",\n" + + " \"clientSecret\": \"testsecret\",\n" + + " \"discoveryURI\": \"testdiscoveryuri\",\n" + + " \"scope\": \"testscope\"\n" + + "}\n"; + + OIDCConfig conf = jsonMapper.readValue( + jsonMapper.writeValueAsString(jsonMapper.readValue(jsonStr, OIDCConfig.class)), + OIDCConfig.class + ); + Assert.assertEquals("testid", conf.getClientID()); + Assert.assertEquals("testsecret", conf.getClientSecret().getPassword()); + Assert.assertEquals("testdiscoveryuri", conf.getDiscoveryURI()); + Assert.assertEquals("name", conf.getOidcClaim()); + Assert.assertEquals("testscope", conf.getScope()); + } + + @Test + public void testSerdeWithoutDefaults() throws Exception + { + ObjectMapper jsonMapper = new ObjectMapper(); + + String jsonStr = "{\n" + + " \"clientID\": \"testid\",\n" + + " \"clientSecret\": \"testsecret\",\n" + + " \"discoveryURI\": \"testdiscoveryuri\",\n" + + " \"oidcClaim\": \"email\",\n" + + " \"scope\": \"testscope\"\n" + + "}\n"; + + OIDCConfig conf = jsonMapper.readValue( + jsonMapper.writeValueAsString(jsonMapper.readValue(jsonStr, OIDCConfig.class)), + OIDCConfig.class + ); + + Assert.assertEquals("testid", conf.getClientID()); + Assert.assertEquals("testsecret", conf.getClientSecret().getPassword()); + Assert.assertEquals("testdiscoveryuri", conf.getDiscoveryURI()); + Assert.assertEquals("email", conf.getOidcClaim()); + Assert.assertEquals("testscope", conf.getScope()); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java new file mode 100644 index 000000000000..00c77bac98c3 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jCommonConfigTest.java @@ -0,0 +1,49 @@ +/* + * 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.druid.security.pac4j; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.druid.jackson.DefaultObjectMapper; +import org.junit.Assert; +import org.junit.Test; + +public class Pac4jCommonConfigTest +{ + @Test + public void testSerde() throws Exception + { + ObjectMapper jsonMapper = new DefaultObjectMapper(); + + String jsonStr = "{\n" + + " \"cookiePassphrase\": \"testpass\",\n" + + " \"readTimeout\": \"PT10S\",\n" + + " \"enableCustomSslContext\": true\n" + + "}\n"; + + Pac4jCommonConfig conf = jsonMapper.readValue( + jsonMapper.writeValueAsString(jsonMapper.readValue(jsonStr, Pac4jCommonConfig.class)), + Pac4jCommonConfig.class + ); + + Assert.assertEquals("testpass", conf.getCookiePassphrase().getPassword()); + Assert.assertEquals(10_000L, conf.getReadTimeout().getMillis()); + Assert.assertTrue(conf.isEnableCustomSslContext()); + } +} diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java new file mode 100644 index 000000000000..bd90693d52db --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -0,0 +1,77 @@ +/* + * 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.druid.security.pac4j; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.pac4j.core.exception.http.ForbiddenAction; +import org.pac4j.core.exception.http.FoundAction; +import org.pac4j.core.exception.http.HttpAction; +import org.pac4j.core.exception.http.WithLocationAction; +import org.pac4j.jee.context.JEEContext; +import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import static org.mockito.ArgumentMatchers.any; + +@RunWith(MockitoJUnitRunner.class) +public class Pac4jFilterTest +{ + + @Mock + private HttpServletRequest request; + @Mock + private HttpServletResponse response; + private JEEContext context; + + @Before + public void setUp() + { + context = new JEEContext(request, response); + } + + @Test + public void testActionAdapterForRedirection() + { + HttpAction httpAction = new FoundAction("testUrl"); + Mockito.doReturn(httpAction.getCode()).when(response).getStatus(); + Mockito.doReturn(((WithLocationAction) httpAction).getLocation()).when(response).getHeader(any()); + JEEHttpActionAdapter.INSTANCE.adapt(httpAction, context); + Assert.assertEquals(response.getStatus(), 302); + Assert.assertEquals(response.getHeader("Location"), "testUrl"); + } + + @Test + public void testActionAdapterForForbidden() + { + HttpAction httpAction = ForbiddenAction.INSTANCE; + Mockito.doReturn(httpAction.getCode()).when(response).getStatus(); + JEEHttpActionAdapter.INSTANCE.adapt(httpAction, context); + Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_FORBIDDEN); + } + +} diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jSessionStoreTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jSessionStoreTest.java new file mode 100644 index 000000000000..772bef7ef6c3 --- /dev/null +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jSessionStoreTest.java @@ -0,0 +1,134 @@ +/* + * 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.druid.security.pac4j; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.junit.Assert; +import org.junit.Test; +import org.pac4j.core.context.Cookie; +import org.pac4j.core.context.WebContext; +import org.pac4j.core.profile.CommonProfile; +import org.pac4j.core.profile.definition.CommonProfileDefinition; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +public class Pac4jSessionStoreTest +{ + private static final String COOKIE_PASSPHRASE = "test-cookie-passphrase"; + + @Test + public void testSetAndGet() + { + Pac4jSessionStore sessionStore = new Pac4jSessionStore(COOKIE_PASSPHRASE); + + WebContext webContext1 = EasyMock.mock(WebContext.class); + EasyMock.expect(webContext1.getScheme()).andReturn("https"); + Capture cookieCapture = EasyMock.newCapture(); + + webContext1.addResponseCookie(EasyMock.capture(cookieCapture)); + EasyMock.replay(webContext1); + + sessionStore.set(webContext1, "key", "value"); + + Cookie cookie = cookieCapture.getValue(); + Assert.assertTrue(cookie.isSecure()); + Assert.assertTrue(cookie.isHttpOnly()); + Assert.assertTrue(cookie.isSecure()); + Assert.assertEquals(900, cookie.getMaxAge()); + + + WebContext webContext2 = EasyMock.mock(WebContext.class); + EasyMock.expect(webContext2.getRequestCookies()).andReturn(Collections.singletonList(cookie)); + EasyMock.replay(webContext2); + Assert.assertEquals("value", Objects.requireNonNull(sessionStore.get(webContext2, "key")).orElse(null)); + } + + @Test + public void testSetAndGetClearUserProfile() + { + Pac4jSessionStore sessionStore = new Pac4jSessionStore(COOKIE_PASSPHRASE); + + WebContext webContext1 = EasyMock.mock(WebContext.class); + EasyMock.expect(webContext1.getScheme()).andReturn("https"); + Capture cookieCapture = EasyMock.newCapture(); + + webContext1.addResponseCookie(EasyMock.capture(cookieCapture)); + EasyMock.replay(webContext1); + + CommonProfile profile = new CommonProfile(); + profile.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name"); + sessionStore.set(webContext1, "pac4jUserProfiles", profile); + + Cookie cookie = cookieCapture.getValue(); + Assert.assertTrue(cookie.isSecure()); + Assert.assertTrue(cookie.isHttpOnly()); + Assert.assertTrue(cookie.isSecure()); + Assert.assertEquals(900, cookie.getMaxAge()); + + + WebContext webContext2 = EasyMock.mock(WebContext.class); + EasyMock.expect(webContext2.getRequestCookies()).andReturn(Collections.singletonList(cookie)); + EasyMock.replay(webContext2); + Optional value = sessionStore.get(webContext2, "pac4jUserProfiles"); + Assert.assertTrue(Objects.requireNonNull(value).isPresent()); + Assert.assertEquals("name", ((CommonProfile) value.get()).getAttribute(CommonProfileDefinition.DISPLAY_NAME)); + } + + @Test + public void testSetAndGetClearUserMultipleProfile() + { + Pac4jSessionStore sessionStore = new Pac4jSessionStore(COOKIE_PASSPHRASE); + + WebContext webContext1 = EasyMock.mock(WebContext.class); + EasyMock.expect(webContext1.getScheme()).andReturn("https"); + Capture cookieCapture = EasyMock.newCapture(); + + webContext1.addResponseCookie(EasyMock.capture(cookieCapture)); + EasyMock.replay(webContext1); + + CommonProfile profile1 = new CommonProfile(); + profile1.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name1"); + CommonProfile profile2 = new CommonProfile(); + profile2.addAttribute(CommonProfileDefinition.DISPLAY_NAME, "name2"); + Map profiles = new HashMap<>(); + profiles.put("profile1", profile1); + profiles.put("profile2", profile2); + sessionStore.set(webContext1, "pac4jUserProfiles", profiles); + + Cookie cookie = cookieCapture.getValue(); + Assert.assertTrue(cookie.isSecure()); + Assert.assertTrue(cookie.isHttpOnly()); + Assert.assertTrue(cookie.isSecure()); + Assert.assertEquals(900, cookie.getMaxAge()); + + + WebContext webContext2 = EasyMock.mock(WebContext.class); + EasyMock.expect(webContext2.getRequestCookies()).andReturn(Collections.singletonList(cookie)); + EasyMock.replay(webContext2); + Optional value = sessionStore.get(webContext2, "pac4jUserProfiles"); + Assert.assertTrue(Objects.requireNonNull(value).isPresent()); + Assert.assertEquals(2, ((Map) value.get()).size()); + } +} diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java index 7b942685d0c0..db7d5affe90c 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jFilter.java @@ -126,4 +126,4 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo public void destroy() { } -} \ No newline at end of file +} diff --git a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java index 4cad5b5da9f4..b0187d5e7293 100644 --- a/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java +++ b/extensions-core/druid-pac4j/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java @@ -209,4 +209,4 @@ public boolean renewSession(final WebContext context) { return false; } -} \ No newline at end of file +} diff --git a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java index 0cea57ab10d0..0523c970178d 100644 --- a/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java +++ b/extensions-core/druid-pac4j/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -74,4 +74,4 @@ public void testActionAdapterForForbidden() Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_FORBIDDEN); } -} \ No newline at end of file +} diff --git a/integration-tests-ex/docs/docker.md b/integration-tests-ex/docs/docker.md index 2250334c7fc3..0bdaef81c713 100644 --- a/integration-tests-ex/docs/docker.md +++ b/integration-tests-ex/docs/docker.md @@ -317,6 +317,7 @@ druid-lookups-cached-global druid-lookups-cached-single druid-orc-extensions druid-pac4j +druid-pac4j-v5 druid-parquet-extensions druid-protobuf-extensions druid-ranger-security diff --git a/licenses.yaml b/licenses.yaml index 42525bd34f4d..162c03570339 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -149,6 +149,16 @@ source_paths: --- +name: code adapted from Apache Knox KnoxSessionStore and ConfigurableEncryptor +license_category: source +module: extensions/druid-pac4j-v5 +license_name: Apache License version 2.0 +source_paths: + - extensions-core/druid-pac4j-v5/src/main/java/org/apache/druid/security/pac4j/Pac4jSessionStore.java + - processing/src/main/java/org/apache/druid/crypto/CryptoService.java + +--- + name: Code adopted from org.apache.commons.dbcp2.BasicDataSource license_category: source module: server @@ -784,6 +794,17 @@ libraries: --- + +name: pac4j-oidc java security library +license_category: binary +module: extensions/druid-pac4j-v5 +license_name: Apache License version 2.0 +version: 5.7.3 +libraries: + - org.pac4j: pac4j-oidc + +--- + name: pac4j-core java security library license_category: binary module: extensions/druid-pac4j @@ -794,10 +815,20 @@ libraries: --- +name: pac4j-core java security library +license_category: binary +module: extensions/druid-pac4j-v5 +license_name: Apache License version 2.0 +version: 5.7.3 +libraries: + - org.pac4j: pac4j-core + +--- name: com.nimbusds lang-tag license_category: binary module: extensions/druid-pac4j +module: extensions/druid-pac4j-v5 license_name: Apache License version 2.0 version: 1.7 libraries: @@ -809,6 +840,16 @@ name: com.nimbusds nimbus-jose-jwt license_category: binary module: extensions/druid-pac4j license_name: Apache License version 2.0 +version: 8.22.1 +libraries: + - com.nimbusds: nimbus-jose-jwt + +--- + +name: com.nimbusds nimbus-jose-jwt +license_category: binary +module: extensions/druid-pac4j-v5 +license_name: Apache License version 2.0 version: 9.37.2 libraries: - com.nimbusds: nimbus-jose-jwt @@ -818,6 +859,7 @@ libraries: name: com.nimbusds content-type license_category: binary module: extensions/druid-pac4j +module: extensions/druid-pac4j-v5 license_name: Apache License version 2.0 version: 2.1 libraries: @@ -828,6 +870,7 @@ libraries: name: com.nimbusds oauth2-oidc-sdk license_category: binary module: extensions/druid-pac4j +module: extensions/druid-pac4j-v5 license_name: Apache License version 2.0 version: 9.37.2 libraries: @@ -838,6 +881,7 @@ libraries: name: javax.activation activation license_category: binary module: extensions/druid-pac4j +module: extensions/druid-pac4j-v5 license_name: COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 version: 1.1.1 libraries: @@ -848,6 +892,7 @@ libraries: name: com.sun.mail javax.mail license_category: binary module: extensions/druid-pac4j +module: extensions/druid-pac4j-v5 license_name: CDDL 1.1 version: 1.6.2 libraries: @@ -2569,6 +2614,7 @@ notices: name: JSON Small and Fast Parser license_category: binary module: druid-pac4j +module: druid-pac4j-v5 license_name: Apache License version 2.0 version: 2.4.11 libraries: @@ -2578,6 +2624,7 @@ libraries: name: JSON Small and Fast Parser license_category: binary module: druid-pac4j +module: druid-pac4j-v5 license_name: Apache License version 2.0 version: 2.4.11 libraries: diff --git a/pom.xml b/pom.xml index f4dd5f0d4512..7267cbffa1e8 100644 --- a/pom.xml +++ b/pom.xml @@ -180,6 +180,7 @@ extensions-core/druid-bloom-filter extensions-core/druid-kerberos extensions-core/druid-pac4j + extensions-core/druid-pac4j-v5 extensions-core/hdfs-storage extensions-core/histogram extensions-core/stats diff --git a/website/.spelling b/website/.spelling index bce41f73a87d..40a4a59c8916 100644 --- a/website/.spelling +++ b/website/.spelling @@ -2361,6 +2361,7 @@ mysql-metadata-storage postgresql-metadata-storage simple-client-sslcontext druid-pac4j +druid-pac4j-v5 druid-kubernetes-extensions aliyun-oss-extensions ambari-metrics-emitter From 671c4f3f1b8ec14d452efa7c6e4c6f35a3485eac Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 18 Jun 2024 12:12:17 +0530 Subject: [PATCH 07/15] Fix spellcheck --- docs/configuration/extensions.md | 2 +- extensions-core/druid-pac4j-v5/pom.xml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/configuration/extensions.md b/docs/configuration/extensions.md index e50bfbb12240..a9e17b02a62b 100644 --- a/docs/configuration/extensions.md +++ b/docs/configuration/extensions.md @@ -63,7 +63,7 @@ Core extensions are maintained by Druid committers. | postgresql-metadata-storage | PostgreSQL metadata store. | [link](../development/extensions-core/postgresql.md) | | simple-client-sslcontext | Simple SSLContext provider module to be used by Druid's internal HttpClient when talking to other Druid processes over HTTPS. | [link](../development/extensions-core/simple-client-sslcontext.md) | | druid-pac4j | OpenID Connect authentication for druid processes. | [link](../development/extensions-core/druid-pac4j.md) | -| druid-pac4j-v5 | OpenID Connect authentication for druid processes. Uses v5+ for pac4j. Incompatible with JDK8. | [link](../development/extensions-core/druid-pac4j-v5.md) | +| druid-pac4j-v5 | OpenID Connect authentication for druid processes. Uses version 5 or above for pac4j. Incompatible with JDK8. | [link](../development/extensions-core/druid-pac4j-v5.md) | |druid-kubernetes-extensions|Druid cluster deployment on Kubernetes without Zookeeper.|[link](../development/extensions-core/kubernetes.md)| diff --git a/extensions-core/druid-pac4j-v5/pom.xml b/extensions-core/druid-pac4j-v5/pom.xml index 3c6f58867608..839671e85e6b 100644 --- a/extensions-core/druid-pac4j-v5/pom.xml +++ b/extensions-core/druid-pac4j-v5/pom.xml @@ -34,6 +34,9 @@ + 11 + 11 + 11 5.7.3 From 56521ca436629b15a3472347fd498c8dac919b2b Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 18 Jun 2024 12:16:34 +0530 Subject: [PATCH 08/15] Remove maven target version from child pom --- extensions-core/druid-pac4j-v5/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/extensions-core/druid-pac4j-v5/pom.xml b/extensions-core/druid-pac4j-v5/pom.xml index 839671e85e6b..f6b6cb6af49b 100644 --- a/extensions-core/druid-pac4j-v5/pom.xml +++ b/extensions-core/druid-pac4j-v5/pom.xml @@ -34,8 +34,6 @@ - 11 - 11 11 5.7.3 From 79d67c7764cc58a2e6f559afc120f426a4939a39 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 18 Jun 2024 13:12:56 +0530 Subject: [PATCH 09/15] Don't include new extension by default --- pom.xml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7267cbffa1e8..c1c3f9953f55 100644 --- a/pom.xml +++ b/pom.xml @@ -180,7 +180,6 @@ extensions-core/druid-bloom-filter extensions-core/druid-kerberos extensions-core/druid-pac4j - extensions-core/druid-pac4j-v5 extensions-core/hdfs-storage extensions-core/histogram extensions-core/stats @@ -1937,6 +1936,9 @@ -Djava.security.manager=allow + + extensions-core/druid-pac4j-v5 + java-9+ @@ -1985,6 +1987,9 @@ + + extensions-core/druid-pac4j-v5 + strict From db91e621b9356eb6b6391477b9d65ce71ca22ee9 Mon Sep 17 00:00:00 2001 From: pagrawal Date: Tue, 18 Jun 2024 16:03:14 +0530 Subject: [PATCH 10/15] Remove pac4j-v5 from dist profile --- distribution/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/distribution/pom.xml b/distribution/pom.xml index 18c1da344a97..136935433e93 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -252,8 +252,6 @@ -c org.apache.druid.extensions:druid-pac4j -c - org.apache.druid.extensions:druid-pac4j-v5 - -c org.apache.druid.extensions:druid-ranger-security -c org.apache.druid.extensions:druid-kubernetes-extensions From f0738c1eda78622104cc0c47ac0de7053a477add Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 19 Jun 2024 10:31:23 +0530 Subject: [PATCH 11/15] Update licenses --- licenses.yaml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/licenses.yaml b/licenses.yaml index 162c03570339..41d2d98c5828 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -869,7 +869,6 @@ libraries: name: com.nimbusds oauth2-oidc-sdk license_category: binary -module: extensions/druid-pac4j module: extensions/druid-pac4j-v5 license_name: Apache License version 2.0 version: 9.37.2 @@ -878,6 +877,16 @@ libraries: --- +name: com.nimbusds oauth2-oidc-sdk +license_category: binary +module: extensions/druid-pac4j +license_name: Apache License version 2.0 +version: 8.22.1 +libraries: + - com.nimbusds: oauth2-oidc-sdk + +--- + name: javax.activation activation license_category: binary module: extensions/druid-pac4j From 6c6ce8f718017cba5c77a609a862bcf64190054c Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 19 Jun 2024 10:57:46 +0530 Subject: [PATCH 12/15] Fix license for oauth2-oidc-sdk --- licenses.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenses.yaml b/licenses.yaml index 41d2d98c5828..2353511a96f1 100644 --- a/licenses.yaml +++ b/licenses.yaml @@ -881,7 +881,7 @@ name: com.nimbusds oauth2-oidc-sdk license_category: binary module: extensions/druid-pac4j license_name: Apache License version 2.0 -version: 8.22.1 +version: 8.22 libraries: - com.nimbusds: oauth2-oidc-sdk From 80b5cbb6cf83066b8a07d7c8a0428950d3c8c4dd Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 19 Jun 2024 17:26:12 +0530 Subject: [PATCH 13/15] Fix unit test --- .../apache/druid/security/pac4j/Pac4jFilterTest.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java index bd90693d52db..dd4bc73a4fab 100644 --- a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -26,16 +26,23 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.pac4j.core.adapter.JEEAdapter; import org.pac4j.core.exception.http.ForbiddenAction; import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.HttpAction; import org.pac4j.core.exception.http.WithLocationAction; +import org.pac4j.jee.adapter.JEEAdapterImpl; import org.pac4j.jee.context.JEEContext; +import org.pac4j.jee.context.session.JEESessionStoreFactory; import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.Writer; + import static org.mockito.ArgumentMatchers.any; @RunWith(MockitoJUnitRunner.class) @@ -66,10 +73,11 @@ public void testActionAdapterForRedirection() } @Test - public void testActionAdapterForForbidden() + public void testActionAdapterForForbidden() throws IOException { HttpAction httpAction = ForbiddenAction.INSTANCE; Mockito.doReturn(httpAction.getCode()).when(response).getStatus(); + Mockito.doReturn(Mockito.mock(PrintWriter.class)).when(context.getNativeResponse()).getWriter(); JEEHttpActionAdapter.INSTANCE.adapt(httpAction, context); Assert.assertEquals(response.getStatus(), HttpServletResponse.SC_FORBIDDEN); } From 58c574afdad609093fa44b103cd5164003eeb51a Mon Sep 17 00:00:00 2001 From: pagrawal Date: Wed, 19 Jun 2024 17:55:08 +0530 Subject: [PATCH 14/15] Fix checkstyle --- .../java/org/apache/druid/security/pac4j/Pac4jFilterTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java index dd4bc73a4fab..096d8b7464bb 100644 --- a/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java +++ b/extensions-core/druid-pac4j-v5/src/test/java/org/apache/druid/security/pac4j/Pac4jFilterTest.java @@ -26,14 +26,11 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; -import org.pac4j.core.adapter.JEEAdapter; import org.pac4j.core.exception.http.ForbiddenAction; import org.pac4j.core.exception.http.FoundAction; import org.pac4j.core.exception.http.HttpAction; import org.pac4j.core.exception.http.WithLocationAction; -import org.pac4j.jee.adapter.JEEAdapterImpl; import org.pac4j.jee.context.JEEContext; -import org.pac4j.jee.context.session.JEESessionStoreFactory; import org.pac4j.jee.http.adapter.JEEHttpActionAdapter; import javax.servlet.http.HttpServletRequest; @@ -41,7 +38,6 @@ import java.io.IOException; import java.io.PrintWriter; -import java.io.Writer; import static org.mockito.ArgumentMatchers.any; From 276ae90dd2b89e901c47ff2b8acea7c3d27e827c Mon Sep 17 00:00:00 2001 From: pagrawal Date: Thu, 11 Jul 2024 10:29:18 +0530 Subject: [PATCH 15/15] Add module during compile time --- distribution/pom.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/distribution/pom.xml b/distribution/pom.xml index 136935433e93..18c1da344a97 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -252,6 +252,8 @@ -c org.apache.druid.extensions:druid-pac4j -c + org.apache.druid.extensions:druid-pac4j-v5 + -c org.apache.druid.extensions:druid-ranger-security -c org.apache.druid.extensions:druid-kubernetes-extensions