This is an automated email from the ASF dual-hosted git repository.
lmccay pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 9b7c4b7c5 KNOX-3107 - Alias Support for the RemoteAuthProvider
truststore password (#1002)
9b7c4b7c5 is described below
commit 9b7c4b7c51bc4ffc1e49702604c1f0b841dd6994
Author: lmccay <[email protected]>
AuthorDate: Sat Mar 8 10:38:08 2025 -0500
KNOX-3107 - Alias Support for the RemoteAuthProvider truststore password
(#1002)
---
.../knox/gateway/filter/RemoteAuthFilter.java | 121 +++++++++-----
.../knox/gateway/filter/RemoteAuthFilterTest.java | 177 ++++++++++++---------
2 files changed, 179 insertions(+), 119 deletions(-)
diff --git
a/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java
b/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java
index bbf5d17aa..29536d35c 100755
---
a/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java
+++
b/gateway-provider-security-authc-remote/src/main/java/org/apache/knox/gateway/filter/RemoteAuthFilter.java
@@ -33,6 +33,8 @@ import org.apache.knox.gateway.security.GroupPrincipal;
import org.apache.knox.gateway.security.PrimaryPrincipal;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.AliasServiceException;
import org.apache.knox.gateway.services.security.KeystoreService;
import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.logging.log4j.ThreadContext;
@@ -80,6 +82,7 @@ public class RemoteAuthFilter implements Filter {
static final String WILDCARD = "*";
static final String TRACE_ID = "trace_id";
static final String REQUEST_ID_HEADER_NAME = "X-Request-Id";
+ static final String
TRUSTSTORE_CONFIGURATION_CANNOT_BE_RESOLVED_INTO_A_VALID_TRUSTSTORE =
"Truststore configuration cannot be resolved into a valid truststore";
private String remoteAuthUrl;
private List<String> includeHeaders;
@@ -98,9 +101,7 @@ public class RemoteAuthFilter implements Filter {
AuditConstants.DEFAULT_AUDITOR_NAME,
AuditConstants.KNOX_SERVICE_NAME, AuditConstants.KNOX_COMPONENT_NAME );
private final RemoteAuthMessages LOGGER = MessagesFactory.get(
RemoteAuthMessages.class );
- private String truststorePath;
- private String truststorePassword;
- private String truststoreType;
+ private KeyStore trustStore;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
@@ -112,9 +113,9 @@ public class RemoteAuthFilter implements Filter {
includeHeaders =
Arrays.asList(filterConfig.getInitParameter(CONFIG_INCLUDE_HEADERS).split(","));
cacheKeyHeader = filterConfig.getInitParameter(CONFIG_CACHE_KEY_HEADER) !=
null ? filterConfig
.getInitParameter(CONFIG_CACHE_KEY_HEADER) :
DEFAULT_CACHE_KEY_HEADER;
- String cachetime = filterConfig.getInitParameter(CONFIG_EXPIRE_AFTER);
- if (cachetime != null) {
- int expireAfterMinutes = Integer.parseInt(cachetime);
+ String cacheTime = filterConfig.getInitParameter(CONFIG_EXPIRE_AFTER);
+ if (cacheTime != null) {
+ int expireAfterMinutes = Integer.parseInt(cacheTime);
authenticationCache = CacheBuilder.newBuilder()
.expireAfterWrite(expireAfterMinutes, TimeUnit.MINUTES)
.build();
@@ -132,12 +133,74 @@ public class RemoteAuthFilter implements Filter {
groupHeaders = Arrays.asList(groupHeaderParam.split("\\s*,\\s*"));
}
- truststorePath = filterConfig.getInitParameter(CONFIG_TRUSTSTORE_PATH);
- truststorePassword =
filterConfig.getInitParameter(CONFIG_TRUSTSTORE_PASSWORD);
- truststoreType = filterConfig.getInitParameter(CONFIG_TRUSTSTORE_TYPE);
+ buildTrustStore(filterConfig);
+ }
+
+ private void buildTrustStore(FilterConfig filterConfig) throws
ServletException {
+ String truststorePath =
filterConfig.getInitParameter(CONFIG_TRUSTSTORE_PATH);
+ String truststorePassword =
filterConfig.getInitParameter(CONFIG_TRUSTSTORE_PASSWORD);
+ String truststoreType =
filterConfig.getInitParameter(CONFIG_TRUSTSTORE_TYPE);
if (truststoreType == null || truststoreType.isEmpty()) {
truststoreType = DEFAULT_TRUSTSTORE_TYPE;
}
+
+ ServletContext context = filterConfig.getServletContext();
+ if (context != null) {
+ String topologyName = (String)
context.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE);
+ GatewayServices services = (GatewayServices)
context.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
+ if (services != null) {
+ try {
+ final AliasService aliasService =
services.getService(ServiceType.ALIAS_SERVICE);
+ if (truststorePath != null && !truststorePath.isEmpty()) {
+ if (truststorePassword == null || truststorePassword.isEmpty()) {
+ // let's check for an alias given the intent to specify a
truststore path
+ char[] passChars =
aliasService.getPasswordFromAliasForCluster(topologyName,
+ CONFIG_TRUSTSTORE_PASSWORD, false);
+ if (passChars != null) {
+ truststorePassword = new String(passChars);
+ }
+ if (truststorePassword == null || truststorePassword.isEmpty()) {
+ truststorePassword = new
String(aliasService.getPasswordFromAliasForGateway(CONFIG_TRUSTSTORE_PASSWORD));
+ }
+ }
+ }
+ KeystoreService keystoreService =
services.getService(ServiceType.KEYSTORE_SERVICE);
+ trustStore = getTrustStore(truststorePath, truststoreType,
truststorePassword, keystoreService);
+ } catch (AliasServiceException | IOException e) {
+ throw new ServletException("Error while initializing
RemoteAuthProvider", e);
+ }
+ }
+ }
+ if (trustStore == null) {
+ // truststore details were explicitly configured but there is no servlet
context available for gateway services
+ throw new
ServletException(TRUSTSTORE_CONFIGURATION_CANNOT_BE_RESOLVED_INTO_A_VALID_TRUSTSTORE);
+ }
+ }
+
+ private KeyStore getTrustStore(String truststorePath, String truststoreType,
String truststorePassword,
+ KeystoreService keystoreService) throws
IOException {
+ KeyStore truststore = null;
+ try {
+ // Try topology-specific truststore first if configured
+ if (truststorePath != null && !truststorePath.isEmpty()) {
+ truststore = keystoreService.loadTruststore(truststorePath,
truststoreType, truststorePassword);
+ if (truststore == null) {
+ // truststore details were explicitly configured but there is no
truststore realized by that config
+ throw new
IOException(TRUSTSTORE_CONFIGURATION_CANNOT_BE_RESOLVED_INTO_A_VALID_TRUSTSTORE);
+ }
+ }
+ // Fall back to gateway-level truststore
+ if (truststore == null) {
+ truststore = keystoreService.getTruststoreForHttpClient();
+ if (truststore == null) {
+ truststore = keystoreService.getKeystoreForGateway();
+ }
+ }
+ } catch (KeystoreServiceException e) {
+ LOGGER.failedToLoadTruststore(e.getMessage(), e);
+ throw new IOException("Failed to load truststore: ", e);
+ }
+ return truststore;
}
public SSLSocketFactory createSSLSocketFactory(KeyStore trustStore) throws
Exception {
@@ -164,7 +227,7 @@ public class RemoteAuthFilter implements Filter {
}
try {
- HttpURLConnection connection =
getHttpURLConnection(request.getServletContext());
+ HttpURLConnection connection = getHttpURLConnection();
for (String header : includeHeaders) {
String headerValue = httpRequest.getHeader(header);
if (headerValue != null) {
@@ -211,22 +274,14 @@ public class RemoteAuthFilter implements Filter {
}
}
- private HttpURLConnection getHttpURLConnection(ServletContext
servletContext) throws IOException {
- KeyStore truststore = null;
- GatewayServices services = (GatewayServices)
servletContext.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
- if (services != null) {
- KeystoreService keystoreService =
services.getService(ServiceType.KEYSTORE_SERVICE);
- if (keystoreService != null) {
- truststore = getTrustStore(truststore, keystoreService);
- }
- }
+ private HttpURLConnection getHttpURLConnection() throws IOException {
HttpURLConnection connection;
if (httpURLConnection == null) {
URL url = new URL(remoteAuthUrl);
connection = (HttpURLConnection) url.openConnection();
- if (truststore != null) {
+ if (trustStore != null) {
try {
- ((HttpsURLConnection)
connection).setSSLSocketFactory(createSSLSocketFactory(truststore));
+ ((HttpsURLConnection)
connection).setSSLSocketFactory(createSSLSocketFactory(trustStore));
} catch (Exception e) {
throw new RuntimeException(e);
}
@@ -237,27 +292,9 @@ public class RemoteAuthFilter implements Filter {
return connection;
}
- private KeyStore getTrustStore(KeyStore truststore, KeystoreService
keystoreService) throws IOException {
- try {
- // Try topology-specific truststore first if configured
- if (truststorePath != null && !truststorePath.isEmpty()) {
- truststore = keystoreService.loadTruststore(truststorePath,
truststoreType, truststorePassword);
- }
- // Fall back to gateway-level truststore
- if (truststore == null) {
- truststore = keystoreService.getTruststoreForHttpClient();
- if (truststore == null) {
- truststore = keystoreService.getKeystoreForGateway();
- }
- }
- } catch (KeystoreServiceException e) {
- LOGGER.failedToLoadTruststore(e.getMessage(), e);
- throw new IOException("Failed to load truststore: ", e);
- }
- return truststore;
- }
-
- private void continueWithEstablishedSecurityContext(Subject subject, final
HttpServletRequest request, final HttpServletResponse response, final
FilterChain chain) throws IOException, ServletException {
+ private void continueWithEstablishedSecurityContext(Subject subject, final
HttpServletRequest request,
+ final
HttpServletResponse response, final FilterChain chain)
+ throws IOException, ServletException {
try {
Subject.doAs(
subject,
diff --git
a/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java
b/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java
index f81af57e5..0c269a380 100644
---
a/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java
+++
b/gateway-provider-security-authc-remote/src/test/java/org/apache/knox/gateway/filter/RemoteAuthFilterTest.java
@@ -21,8 +21,8 @@ import org.apache.knox.gateway.security.GroupPrincipal;
import org.apache.knox.gateway.security.PrimaryPrincipal;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.ServiceType;
+import org.apache.knox.gateway.services.security.AliasService;
import org.apache.knox.gateway.services.security.KeystoreService;
-import org.apache.knox.gateway.services.security.KeystoreServiceException;
import org.apache.knox.test.mock.MockServletContext;
import org.easymock.EasyMock;
import org.junit.Before;
@@ -51,14 +51,24 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.security.KeyStore;
+import java.security.KeyStoreException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+/**
+ * Tests for RemoteAuthFilter focusing on:
+ * - Initialization flow and error handling
+ * - Truststore configuration processing during init (not per-request)
+ * - Fast failure with invalid truststore configuration
+ * - Integration between the filter and KeystoreService
+ *
+ * Note: These tests use mocks for the KeystoreService and don't test actual
SSL/TLS
+ * connections or certificate validation.
+ */
@SuppressWarnings("PMD.JUnit4TestShouldUseBeforeAnnotation")
public class RemoteAuthFilterTest {
-
public static final String BEARER_INVALID_TOKEN = "Bearer invalid-token";
public static final String BEARER_VALID_TOKEN = "Bearer valid-token";
public static final String URL_SUCCESS = "https://example.com/auth";
@@ -75,35 +85,70 @@ public class RemoteAuthFilterTest {
private GatewayServices gatewayServicesMock;
private KeystoreService keystoreServiceMock;
private ServletContext servletContextMock;
+ private AliasService aliasServiceMock;
+ private FilterConfig filterConfigMock;
@Before
public void createMocks() {
requestMock = EasyMock.createMock(HttpServletRequest.class);
responseMock = EasyMock.createMock(HttpServletResponse.class);
+ filterConfigMock = EasyMock.createNiceMock(FilterConfig.class);
+ keystoreServiceMock = EasyMock.createNiceMock(KeystoreService.class);
+ aliasServiceMock = EasyMock.createNiceMock(AliasService.class);
+ servletContextMock = EasyMock.createNiceMock(ServletContext.class);
+ gatewayServicesMock = EasyMock.createNiceMock(GatewayServices.class);
}
- private void setUp(String trustStorePath, String trustStorePass, String
trustStoreType) {
- // Reset existing mocks
- EasyMock.reset(requestMock, responseMock);
+ private void setUp(String trustStorePath, String trustStorePass, String
trustStoreType) throws Exception {
+ // Reset ALL mocks
+ EasyMock.reset(requestMock, responseMock, filterConfigMock,
gatewayServicesMock,
+ servletContextMock, keystoreServiceMock,
aliasServiceMock);
- FilterConfig filterConfigMock =
EasyMock.createNiceMock(FilterConfig.class);
chainMock = new TestFilterChain();
- // Create and configure Gateway Services mocks
- gatewayServicesMock = EasyMock.createNiceMock(GatewayServices.class);
- keystoreServiceMock = EasyMock.createNiceMock(KeystoreService.class);
- servletContextMock = EasyMock.createNiceMock(ServletContext.class);
+ // Set up FilterConfig's ServletContext association FIRST
+ EasyMock.expect(filterConfigMock.getServletContext())
+ .andReturn(servletContextMock)
+ .anyTimes();
+
+ // Now set up ServletContext expectations
+
EasyMock.expect(servletContextMock.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE))
+ .andReturn("test-topology")
+ .anyTimes();
+
EasyMock.expect(servletContextMock.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE))
+ .andReturn(gatewayServicesMock)
+ .anyTimes();
// Set up Gateway Services expectations
EasyMock.expect(gatewayServicesMock.getService(ServiceType.KEYSTORE_SERVICE))
.andReturn(keystoreServiceMock)
.anyTimes();
-
EasyMock.expect(servletContextMock.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE))
- .andReturn(gatewayServicesMock)
+
EasyMock.expect(gatewayServicesMock.getService(ServiceType.ALIAS_SERVICE))
+ .andReturn(aliasServiceMock)
.anyTimes();
+ try {
+ // Set up default truststore expectation for when no explicit
truststore is configured
+ KeyStore defaultTruststore = KeyStore.getInstance("JKS");
+ EasyMock.expect(keystoreServiceMock.getTruststoreForHttpClient())
+ .andReturn(defaultTruststore)
+ .anyTimes();
+ } catch (KeyStoreException e) {
+ throw new RuntimeException(e);
+ }
+
+
EasyMock.expect(aliasServiceMock.getPasswordFromAliasForCluster("test-topology",
+ "remote.auth.truststore.password", false))
+ .andReturn("password".toCharArray())
+ .anyTimes();
+
EasyMock.expect(aliasServiceMock.getPasswordFromAliasForGateway("remote.auth.truststore.password"))
+ .andReturn("password".toCharArray())
+ .anyTimes();
+
// Basic config
-
EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_REMOTE_AUTH_URL)).andReturn("https://example.com/auth").anyTimes();
+
EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_REMOTE_AUTH_URL))
+ .andReturn("https://example.com/auth")
+ .anyTimes();
EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_INCLUDE_HEADERS)).andReturn("Authorization").anyTimes();
EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.DEFAULT_CACHE_KEY_HEADER)).andReturn("Authorization").anyTimes();
EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_EXPIRE_AFTER)).andReturn("5").anyTimes();
@@ -117,18 +162,21 @@ public class RemoteAuthFilterTest {
EasyMock.expect(filterConfigMock.getInitParameter(RemoteAuthFilter.CONFIG_TRUSTSTORE_TYPE)).andReturn(trustStoreType).anyTimes();
// Only replay the mocks that won't need additional expectations
- EasyMock.replay(filterConfigMock, gatewayServicesMock,
servletContextMock);
+ EasyMock.replay(filterConfigMock, gatewayServicesMock,
servletContextMock, keystoreServiceMock, aliasServiceMock);
filter = new RemoteAuthFilter();
try {
filter.init(filterConfigMock);
} catch (ServletException e) {
- throw new RuntimeException(e);
+ if
(!RemoteAuthFilter.TRUSTSTORE_CONFIGURATION_CANNOT_BE_RESOLVED_INTO_A_VALID_TRUSTSTORE.equals(
+ e.getCause().getMessage())) {
+ throw new RuntimeException(e);
+ }
}
}
// Default setup method for backward compatibility
- private void setUp() {
+ private void setUp() throws Exception {
setUp(null, null, null);
}
@@ -339,44 +387,40 @@ public class RemoteAuthFilterTest {
@Test
public void testSuccessfulHttpsRequestWithTrustStore() throws Exception {
- // Setup with valid trust store configuration
- setUp("/path/to/truststore.jks", "trustpass", "JKS");
-
- KeyStore testTruststore = KeyStore.getInstance("JKS");
-
EasyMock.expect(keystoreServiceMock.loadTruststore("/path/to/truststore.jks",
"JKS", "trustpass"))
- .andReturn(testTruststore)
+ // Set up ServletContext expectations
+
EasyMock.expect(servletContextMock.getAttribute(GatewayServices.GATEWAY_CLUSTER_ATTRIBUTE))
+ .andReturn("test-topology")
.anyTimes();
-
- EasyMock.expect(requestMock.getServletContext())
- .andReturn(servletContextMock)
+
EasyMock.expect(servletContextMock.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE))
+ .andReturn(gatewayServicesMock)
.anyTimes();
- EasyMock.expect(requestMock.getHeader("Authorization"))
- .andReturn(BEARER_VALID_TOKEN)
+
+ // Set up GatewayServices expectations
+
EasyMock.expect(gatewayServicesMock.getService(ServiceType.ALIAS_SERVICE))
+ .andReturn(aliasServiceMock)
.anyTimes();
- EasyMock.expect(responseMock.getStatus())
- .andReturn(200)
+
EasyMock.expect(gatewayServicesMock.getService(ServiceType.KEYSTORE_SERVICE))
+ .andReturn(keystoreServiceMock)
.anyTimes();
-
responseMock.sendError(EasyMock.eq(HttpServletResponse.SC_UNAUTHORIZED),
EasyMock.anyString());
- EasyMock.expectLastCall().andThrow(new AssertionError("Authentication
should be successful, but was not.")).anyTimes();
-
- EasyMock.replay(requestMock, responseMock, keystoreServiceMock);
- setupURLConnection("https://example.com/auth");
- filter.doFilter(requestMock, responseMock, chainMock);
+ // Set up keystoreService expectations before filter initialization
+ KeyStore testTruststore = KeyStore.getInstance("JKS");
+
EasyMock.expect(keystoreServiceMock.loadTruststore("/path/to/truststore.jks",
"JKS", "trustpass"))
+ .andReturn(testTruststore)
+ .anyTimes();
- assertTrue("Filter chain should have been called",
chainMock.doFilterCalled);
- }
+ // Set up aliasService expectations for password resolution
+
EasyMock.expect(aliasServiceMock.getPasswordFromAliasForCluster("test-topology",
+ RemoteAuthFilter.CONFIG_TRUSTSTORE_PASSWORD, false))
+ .andReturn("trustpass".toCharArray())
+ .anyTimes();
- @Test
- public void testHttpsRequestWithoutTrustStore() throws Exception {
- // Setup without trust store configuration
- setUp(null, null, null);
+ EasyMock.replay(servletContextMock, gatewayServicesMock,
keystoreServiceMock, aliasServiceMock);
- KeyStore defaultTruststore = KeyStore.getInstance("JKS");
- EasyMock.expect(keystoreServiceMock.getTruststoreForHttpClient())
- .andReturn(defaultTruststore)
- .anyTimes();
+ // Setup with valid trust store configuration - this will now trigger
truststore loading
+ setUp("/path/to/truststore.jks", null, "JKS"); // null password to
test alias resolution
+ // Regular request expectations
EasyMock.expect(requestMock.getServletContext())
.andReturn(servletContextMock)
.anyTimes();
@@ -386,44 +430,23 @@ public class RemoteAuthFilterTest {
EasyMock.expect(responseMock.getStatus())
.andReturn(200)
.anyTimes();
-
responseMock.sendError(EasyMock.eq(HttpServletResponse.SC_UNAUTHORIZED),
EasyMock.anyString());
- EasyMock.expectLastCall().andThrow(new AssertionError("Authentication
should be successful, but was not.")).anyTimes();
- EasyMock.replay(requestMock, responseMock, keystoreServiceMock);
+ EasyMock.replay(requestMock, responseMock);
- setupURLConnection("https://example.com/auth");
+ setupURLConnection(URL_SUCCESS);
filter.doFilter(requestMock, responseMock, chainMock);
- assertTrue("Filter chain should have been called with default trust
store", chainMock.doFilterCalled);
- }
-
- @Test
- public void testHttpsRequestWithInvalidTrustStoreConfig() throws Exception
{
- // Setup with invalid trust store configuration
- setUp("/nonexistent/path/truststore.jks", "password", "JKS");
-
-
EasyMock.expect(keystoreServiceMock.loadTruststore("/nonexistent/path/truststore.jks",
"JKS", "password"))
- .andThrow(new KeystoreServiceException("Failed to load
truststore"))
- .anyTimes();
-
- EasyMock.expect(requestMock.getServletContext())
- .andReturn(servletContextMock)
- .anyTimes();
- EasyMock.expect(requestMock.getHeader("Authorization"))
- .andReturn(BEARER_VALID_TOKEN)
- .anyTimes();
- EasyMock.expect(responseMock.getStatus())
- .andReturn(500)
- .anyTimes();
- responseMock.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
"Error processing authentication request");
- EasyMock.expectLastCall().once();
-
- EasyMock.replay(requestMock, responseMock, keystoreServiceMock);
+ assertTrue("Filter chain should have been called",
chainMock.doFilterCalled);
+ EasyMock.verify(keystoreServiceMock, aliasServiceMock);
- filter.doFilter(requestMock, responseMock, chainMock);
+ // Verify the subject was properly set with expected principals
+ Set<PrimaryPrincipal> primaryPrincipals =
chainMock.subject.getPrincipals(PrimaryPrincipal.class);
+ assertEquals("lmccay",
((Principal)primaryPrincipals.toArray()[0]).getName());
- assertFalse("Filter chain should not have been called",
chainMock.doFilterCalled);
- EasyMock.verify(responseMock);
+ Set<GroupPrincipal> groupPrincipals =
chainMock.subject.getPrincipals(GroupPrincipal.class);
+ assertEquals(2, groupPrincipals.size());
+ assertTrue(groupPrincipals.stream().anyMatch(p ->
p.getName().equals("admin")));
+ assertTrue(groupPrincipals.stream().anyMatch(p ->
p.getName().equals("engineers")));
}
public static class MockHttpURLConnection extends HttpURLConnection {