diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java index 2100f68a97..4dad73c42c 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/AbstractHTTPJwtAuthenticator.java @@ -56,7 +56,7 @@ public abstract class AbstractHTTPJwtAuthenticator implements HTTPAuthenticator private static final Pattern BASIC = Pattern.compile("^\\s*Basic\\s.*", Pattern.CASE_INSENSITIVE); private KeyProvider keyProvider; - private JwtVerifier jwtVerifier; + protected JwtVerifier jwtVerifier; private final String jwtHeaderName; private final boolean isDefaultAuthHeader; private final String jwtUrlParameter; diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java deleted file mode 100644 index bf2f82e178..0000000000 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticator.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ - -package com.amazon.dlic.auth.http.jwt.keybyoidc; - -import java.nio.file.Path; - -import org.opensearch.common.settings.Settings; - -import com.amazon.dlic.auth.http.jwt.AbstractHTTPJwtAuthenticator; -import com.amazon.dlic.util.SettingsBasedSSLConfigurator; - -public class HTTPJwtKeyByOpenIdConnectAuthenticator extends AbstractHTTPJwtAuthenticator { - - // private final static Logger log = LogManager.getLogger(HTTPJwtKeyByOpenIdConnectAuthenticator.class); - - public HTTPJwtKeyByOpenIdConnectAuthenticator(Settings settings, Path configPath) { - super(settings, configPath); - } - - protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception { - int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000); - int idpQueuedThreadTimeoutMs = settings.getAsInt("idp_queued_thread_timeout_ms", 2500); - - int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000); - int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10); - String jwksUri = settings.get("jwks_uri"); - - KeySetRetriever keySetRetriever; - if (jwksUri != null && !jwksUri.isBlank()) { - keySetRetriever = new KeySetRetriever( - getSSLConfig(settings, configPath), - settings.getAsBoolean("cache_jwks_endpoint", false), - jwksUri - ); - } else { - keySetRetriever = new KeySetRetriever( - settings.get("openid_connect_url"), - getSSLConfig(settings, configPath), - settings.getAsBoolean("cache_jwks_endpoint", false) - ); - } - - keySetRetriever.setRequestTimeoutMs(idpRequestTimeoutMs); - - SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(keySetRetriever); - - selfRefreshingKeySet.setRequestTimeoutMs(idpRequestTimeoutMs); - selfRefreshingKeySet.setQueuedThreadTimeoutMs(idpQueuedThreadTimeoutMs); - selfRefreshingKeySet.setRefreshRateLimitTimeWindowMs(refreshRateLimitTimeWindowMs); - selfRefreshingKeySet.setRefreshRateLimitCount(refreshRateLimitCount); - - return selfRefreshingKeySet; - } - - private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath) throws Exception { - return new SettingsBasedSSLConfigurator(settings, configPath, "openid_connect_idp").buildSSLConfig(); - } - - @Override - public String getType() { - return "jwt-key-by-oidc"; - } - -} diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPOpenIdAuthenticator.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPOpenIdAuthenticator.java new file mode 100644 index 0000000000..0089b95376 --- /dev/null +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPOpenIdAuthenticator.java @@ -0,0 +1,319 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package com.amazon.dlic.auth.http.jwt.keybyoidc; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.text.ParseException; +import java.util.Map; +import java.util.Optional; + +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.opensearch.OpenSearchSecurityException; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.security.auth.HTTPAuthenticator; +import org.opensearch.security.filter.SecurityRequest; +import org.opensearch.security.filter.SecurityResponse; +import org.opensearch.security.user.AuthCredentials; + +import com.amazon.dlic.auth.http.jwt.AbstractHTTPJwtAuthenticator; +import com.amazon.dlic.util.SettingsBasedSSLConfigurator; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; + +import static org.apache.http.HttpHeaders.AUTHORIZATION; +import static org.apache.http.entity.ContentType.APPLICATION_JSON; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.APPLICATION_JWT; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.CLIENT_ID; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.ISSUER_ID_URL; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.SUB_CLAIM; + +public class HTTPOpenIdAuthenticator implements HTTPAuthenticator { + + private final static Logger log = LogManager.getLogger(HTTPOpenIdAuthenticator.class); + private final Settings settings; + private final Path configPath; + private final int requestTimeoutMs = 10000; + private final SettingsBasedSSLConfigurator.SSLConfig sslConfig; + private final String userInfoEndpoint; + private volatile HTTPJwtKeyByOpenIdConnectAuthenticator openIdJwtAuthenticator; + + public HTTPOpenIdAuthenticator(Settings settings, Path configPath) throws Exception { + this.settings = settings; + this.configPath = configPath; + this.sslConfig = getSSLConfig(settings, configPath); + userInfoEndpoint = settings.get("userinfo_endpoint"); + openIdJwtAuthenticator = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, configPath); + } + + public HTTPJwtKeyByOpenIdConnectAuthenticator getOpenIdJwtAuthenticator() { + if (openIdJwtAuthenticator == null) { + synchronized (this) { + if (openIdJwtAuthenticator == null) { + openIdJwtAuthenticator = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, configPath); + } + } + } + return openIdJwtAuthenticator; + } + + public String getType() { + return "oidc"; + } + + private static SettingsBasedSSLConfigurator.SSLConfig getSSLConfig(Settings settings, Path configPath) throws Exception { + return new SettingsBasedSSLConfigurator(settings, configPath, "openid_connect_idp").buildSSLConfig(); + } + + @Override + public AuthCredentials extractCredentials(SecurityRequest request, ThreadContext context) throws OpenSearchSecurityException { + if (this.userInfoEndpoint != null && !this.userInfoEndpoint.isBlank()) { + return extractCredentials0(request, context); + } + return (this.openIdJwtAuthenticator.extractCredentials(request, context)); + } + + @Override + public Optional reRequestAuthentication(SecurityRequest request, AuthCredentials credentials) { + return Optional.empty(); + } + + // Public for testing + public CloseableHttpClient createHttpClient() { + HttpClientBuilder builder; + builder = HttpClients.custom(); + builder.useSystemProperties(); + if (sslConfig != null) { + builder.setSSLSocketFactory(sslConfig.toSSLConnectionSocketFactory()); + } + + return builder.build(); + } + + /** + * This method performs the logic required for making use of the userinfo_endpoint OIDC feature. + * Per the spec: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo there are 10 verification steps we must perform + * 1. Validate the OP is correct via TLS certificate check + * 2. Validate response is OK + * 3. Validate application type is either JSON or JWT + * 4. Validate response is one of: just signed, just encrypted, or signed then encrypted (encryption MUST NOT occur before signing) + * 5. If response is signed then validate the signature via JWS and confirm the response has "iss" and "aud" claims + * 6. Validate "iss" claim is equal to the issuer ID url + * 7. Validate the "aud" claim is equal to the client ID + * 8. If the client provides a userinfo_encrypted_response_alg value decrypt the response using the keys from registration + * 9. Validate "sub" claim is always present + * 10. Validate "sub" claim matches the ID token + * @param request The SecurityRequest to perform auth on + * @param context The active thread context + * @return AuthCredentials formed through querying the userinfo_endpoint + * @throws OpenSearchSecurityException On failure to extract credentials from the request + */ + public AuthCredentials extractCredentials0(SecurityRequest request, ThreadContext context) throws OpenSearchSecurityException { + + try (CloseableHttpClient httpClient = createHttpClient()) { + + HttpGet httpGet = new HttpGet(this.userInfoEndpoint); + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(requestTimeoutMs) + .setConnectTimeout(requestTimeoutMs) + .build(); + + httpGet.setConfig(requestConfig); + httpGet.addHeader(AUTHORIZATION, request.getHeaders().get(AUTHORIZATION).get(0)); + + // HTTPGet should internally verify the appropriate TLS cert. + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + + if (response.getStatusLine().getStatusCode() < 200 || response.getStatusLine().getStatusCode() >= 300) { + throw new AuthenticatorUnavailableException( + "Error while getting " + this.userInfoEndpoint + ": Invalid status code " + response.getStatusLine().getStatusCode() + ); + } + + HttpEntity httpEntity = response.getEntity(); + + if (httpEntity == null) { + throw new AuthenticatorUnavailableException("Error while getting " + this.userInfoEndpoint + ": Empty response entity"); + } + + String contentType = httpEntity.getContentType().getValue(); + if (!contentType.contains(APPLICATION_JSON.getMimeType()) && !contentType.contains(APPLICATION_JWT)) { + throw new AuthenticatorUnavailableException( + "Error while getting " + this.userInfoEndpoint + ": Invalid content type in response" + ); + } + + String userinfoContent; + + try ( + // got this from ChatGpt & Amazon Q + InputStream inputStream = httpEntity.getContent(); + InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8) + ) { + StringBuilder content = new StringBuilder(); + char[] buffer = new char[8192]; + int bytesRead; + while ((bytesRead = reader.read(buffer)) != -1) { + content.append(buffer, 0, bytesRead); + } + userinfoContent = content.toString(); + } catch (IOException e) { + throw new AuthenticatorUnavailableException( + "Error while getting " + this.userInfoEndpoint + ": Unable to read response content" + ); + } + + JWTClaimsSet claims; + boolean isSigned = contentType.contains(APPLICATION_JWT); + if (contentType.contains(APPLICATION_JWT)) { // We don't need the userinfo_encrypted_response_alg since the + // selfRefreshingKeyProvider has access to the keys + claims = openIdJwtAuthenticator.getJwtClaimsSetFromInfoContent(userinfoContent); + } else { + claims = JWTClaimsSet.parse(userinfoContent); + } + + String id = openIdJwtAuthenticator.getJwtClaimsSet(request).getSubject(); + String missing = validateResponseClaims(claims, id, isSigned); + if (!missing.isBlank()) { + throw new AuthenticatorUnavailableException( + "Error while getting " + this.userInfoEndpoint + ": Missing or invalid required claims in response: " + missing + ); + } + + final String subject = openIdJwtAuthenticator.extractSubject(claims); + if (subject == null) { + log.error("No subject found in JWT token"); + return null; + } + + final String[] roles = openIdJwtAuthenticator.extractRoles(claims); + + AuthCredentials ac = new AuthCredentials(subject, roles); + + for (Map.Entry claim : claims.getClaims().entrySet()) { + ac.addAttribute("attr.jwt." + claim.getKey(), String.valueOf(claim.getValue())); + } + + return ac.markComplete(); + } catch (ParseException e) { + throw new RuntimeException(e); + } + } catch (IOException e) { + throw new AuthenticatorUnavailableException("Error while getting " + this.userInfoEndpoint + ": " + e, e); + } + } + + private String validateResponseClaims(JWTClaimsSet claims, String id, boolean isSigned) { + + String missing = ""; + + if (claims.getClaim(SUB_CLAIM) == null || claims.getClaim(SUB_CLAIM).toString().isBlank() || !claims.getClaim("sub").equals(id)) { + missing = missing.concat(SUB_CLAIM); + } + + if (isSigned) { + if (claims.getIssuer() == null || claims.getIssuer().isBlank() || !claims.getIssuer().equals(settings.get(ISSUER_ID_URL))) { + missing = missing.concat("iss"); + } + if (claims.getAudience() == null + || claims.getAudience().toString().isBlank() + || !claims.getAudience().contains(settings.get(CLIENT_ID))) { + missing = missing.concat("aud"); + } + } + + return missing; + } + + private final class HTTPJwtKeyByOpenIdConnectAuthenticator extends AbstractHTTPJwtAuthenticator { + + public HTTPJwtKeyByOpenIdConnectAuthenticator(Settings settings, Path configPath) { + super(settings, configPath); + } + + protected KeyProvider initKeyProvider(Settings settings, Path configPath) throws Exception { + int idpRequestTimeoutMs = settings.getAsInt("idp_request_timeout_ms", 5000); + int idpQueuedThreadTimeoutMs = settings.getAsInt("idp_queued_thread_timeout_ms", 2500); + + int refreshRateLimitTimeWindowMs = settings.getAsInt("refresh_rate_limit_time_window_ms", 10000); + int refreshRateLimitCount = settings.getAsInt("refresh_rate_limit_count", 10); + String jwksUri = settings.get("jwks_uri"); + KeySetRetriever keySetRetriever; + + if (jwksUri != null && !jwksUri.isBlank()) { + keySetRetriever = new KeySetRetriever( + getSSLConfig(settings, configPath), + settings.getAsBoolean("cache_jwks_endpoint", false), + jwksUri + ); + } else { + keySetRetriever = new KeySetRetriever( + settings.get("openid_connect_url"), + getSSLConfig(settings, configPath), + settings.getAsBoolean("cache_jwks_endpoint", false) + ); + } + + keySetRetriever.setRequestTimeoutMs(idpRequestTimeoutMs); + + SelfRefreshingKeySet selfRefreshingKeySet = new SelfRefreshingKeySet(keySetRetriever); + + selfRefreshingKeySet.setRequestTimeoutMs(idpRequestTimeoutMs); + selfRefreshingKeySet.setQueuedThreadTimeoutMs(idpQueuedThreadTimeoutMs); + selfRefreshingKeySet.setRefreshRateLimitTimeWindowMs(refreshRateLimitTimeWindowMs); + selfRefreshingKeySet.setRefreshRateLimitCount(refreshRateLimitCount); + + return selfRefreshingKeySet; + } + + private JWTClaimsSet getJwtClaimsSet(SecurityRequest request) throws OpenSearchSecurityException { + String parsedToken = super.getJwtTokenString(request); + return getJwtClaimsSetFromInfoContent(parsedToken); + } + + private JWTClaimsSet getJwtClaimsSetFromInfoContent(String userInfoContent) throws OpenSearchSecurityException { + SignedJWT jwt; + JWTClaimsSet claimsSet; + try { + jwt = super.jwtVerifier.getVerifiedJwtToken(userInfoContent); + claimsSet = jwt.getJWTClaimsSet(); + } catch (OpenSearchSecurityException | ParseException | BadCredentialsException e) { + throw new RuntimeException(e); + } + return claimsSet; + } + + @Override + public AuthCredentials extractCredentials(SecurityRequest request, ThreadContext context) throws OpenSearchSecurityException { + return super.extractCredentials(request, context); + } + + @Override + public String getType() { + return "jwt-key-by-oidc"; + } + } +} diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java index 3e30c2d10a..521a93f415 100644 --- a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/KeySetRetriever.java @@ -53,6 +53,7 @@ public class KeySetRetriever implements KeySetProvider { private long oidcRequests = 0; private long lastCacheStatusLog = 0; private String jwksUri; + private String userInfoEndpoint; KeySetRetriever(String openIdConnectEndpoint, SSLConfig sslConfig, boolean useCacheForOidConnectEndpoint) { this.openIdConnectEndpoint = openIdConnectEndpoint; @@ -237,6 +238,10 @@ private void configureCache(boolean useCacheForOidConnectEndpoint) { } } + public void setUserInfoEndpoint(String userInfoEndpoint) { + this.userInfoEndpoint = userInfoEndpoint; + } + public int getOidcCacheHits() { return oidcCacheHits; } diff --git a/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/OpenIdConstants.java b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/OpenIdConstants.java new file mode 100644 index 0000000000..c20159eeab --- /dev/null +++ b/src/main/java/com/amazon/dlic/auth/http/jwt/keybyoidc/OpenIdConstants.java @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package com.amazon.dlic.auth.http.jwt.keybyoidc; + +public class OpenIdConstants { + + public static final String APPLICATION_JWT = "application/jwt"; + public static final String CLIENT_ID = "client_id"; + public static final String ISSUER_ID_URL = "issuer_id_url"; + public static final String SUB_CLAIM = "sub"; + +} diff --git a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java index 064f555a75..3ad4e62928 100644 --- a/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java +++ b/src/main/java/org/opensearch/security/securityconf/DynamicConfigModel.java @@ -136,7 +136,7 @@ public DynamicConfigModel() { authImplMap.put("clientcert_h", HTTPClientCertAuthenticator.class.getName()); authImplMap.put("kerberos_h", "com.amazon.dlic.auth.http.kerberos.HTTPSpnegoAuthenticator"); authImplMap.put("jwt_h", "com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator"); - authImplMap.put("openid_h", "com.amazon.dlic.auth.http.jwt.keybyoidc.HTTPJwtKeyByOpenIdConnectAuthenticator"); + authImplMap.put("openid_h", "com.amazon.dlic.auth.http.jwt.keybyoidc.HTTPOpenIdAuthenticator"); authImplMap.put("saml_h", "com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator"); authImplMap.put("ip_authFailureListener", AddressBasedRateLimiter.class.getName()); diff --git a/src/main/java/org/opensearch/security/support/ModuleType.java b/src/main/java/org/opensearch/security/support/ModuleType.java index 642d05a09a..9b8efcbc36 100644 --- a/src/main/java/org/opensearch/security/support/ModuleType.java +++ b/src/main/java/org/opensearch/security/support/ModuleType.java @@ -59,7 +59,7 @@ public enum ModuleType implements Serializable { JWT_AUTHENTICATION_BACKEND("JWT authentication backend", "com.amazon.dlic.auth.http.jwt.HTTPJwtAuthenticator", Boolean.TRUE), OPENID_AUTHENTICATION_BACKEND( "OpenID authentication backend", - "com.amazon.dlic.auth.http.jwt.keybyoidc.HTTPJwtKeyByOpenIdConnectAuthenticator", + "com.amazon.dlic.auth.http.jwt.keybyoidc.HTTPOpenIdAuthenticator", Boolean.TRUE ), SAML_AUTHENTICATION_BACKEND("SAML authentication backend", "com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator", Boolean.TRUE), diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java deleted file mode 100644 index 6253d2ca72..0000000000 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * SPDX-License-Identifier: Apache-2.0 - * - * The OpenSearch Contributors require contributions made to - * this file be licensed under the Apache-2.0 license or a - * compatible open source license. - * - * Modifications Copyright OpenSearch Contributors. See - * GitHub history for details. - */ -package com.amazon.dlic.auth.http.jwt.keybyoidc; - -import java.util.HashMap; -import java.util.List; - -import com.google.common.collect.ImmutableMap; -import org.junit.AfterClass; -import org.junit.Assert; -import org.junit.BeforeClass; -import org.junit.Test; - -import org.opensearch.OpenSearchSecurityException; -import org.opensearch.common.settings.Settings; -import org.opensearch.security.user.AuthCredentials; -import org.opensearch.security.util.FakeRestRequest; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public class HTTPJwtKeyByOpenIdConnectAuthenticatorTest { - - protected static MockIpdServer mockIdpServer; - - @BeforeClass - public static void setUp() throws Exception { - mockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL); - } - - @AfterClass - public static void tearDown() { - if (mockIdpServer != null) { - try { - mockIdpServer.close(); - } catch (Exception e) {} - } - } - - @Test - public void basicTest() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - - @Test - public void jwksUriTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - - @Test - public void jwksMissingRequiredIssuerInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_ISSUER_OCT_1), new HashMap<>()) - .asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void jwksNotMatchingRequiredIssuerInClaimTest() { - Settings settings = Settings.builder().put("jwks_uri", mockIdpServer.getJwksUri()).put("required_issuer", "Wrong Issuer").build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void jwksMatchAtLeastOneRequiredAudienceInClaimTest() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE + ",another_audience") - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - - @Test - public void jwksMissingRequiredAudienceInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_AUDIENCE_OCT_1), new HashMap<>()) - .asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void jwksNotMatchingRequiredAudienceInClaimTest() { - Settings settings = Settings.builder() - .put("jwks_uri", mockIdpServer.getJwksUri()) - .put("required_audience", "Wrong Audience") - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void jwksUriMissingTest() { - var exception = Assert.assertThrows(Exception.class, () -> { - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(Settings.builder().build(), null); - jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()).asSecurityRequest(), - null - ); - }); - - assertThat(exception.getMessage(), is("Authentication backend failed")); - assertThat(exception.getClass(), is(OpenSearchSecurityException.class)); - } - - @Test - public void testEscapeKid() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1_INVALID_KID), - new HashMap() - ).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - - @Test - public void bearerTest() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()) - .asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - - @Test - public void testRoles() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("roles_key", TestJwts.ROLES_CLAIM) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()) - .asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getBackendRoles(), is(TestJwts.TEST_ROLES)); - } - - @Test - public void testExp() { - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), new HashMap<>()) - .asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void testExpInSkew() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("jwt_clock_skew_tolerance_seconds", "10") - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - long expiringDate = System.currentTimeMillis() / 1000 - 5; - long notBeforeDate = System.currentTimeMillis() / 1000 - 25; - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), - new HashMap<>() - ).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - } - - @Test - public void testNbf() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("jwt_clock_skew_tolerance_seconds", "0") - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - long expiringDate = 20 + System.currentTimeMillis() / 1000; - long notBeforeDate = 5 + System.currentTimeMillis() / 1000; - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), - new HashMap<>() - ).asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void testNbfInSkew() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("jwt_clock_skew_tolerance_seconds", "10") - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - long expiringDate = 20 + System.currentTimeMillis() / 1000; - long notBeforeDate = 5 + System.currentTimeMillis() / 1000; - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest( - ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), - new HashMap<>() - ).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - } - - @Test - public void testRS256() { - - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - - @Test - public void testBadSignature() { - - Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_X), new HashMap<>()).asSecurityRequest(), - null - ); - - Assert.assertNull(creds); - } - - @Test - public void testPeculiarJsonEscaping() { - Settings settings = Settings.builder() - .put("openid_connect_url", mockIdpServer.getDiscoverUri()) - .put("required_issuer", TestJwts.TEST_ISSUER) - .put("required_audience", TestJwts.TEST_AUDIENCE) - .build(); - - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); - - AuthCredentials creds = jwtAuth.extractCredentials( - new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.PeculiarEscaping.MC_COY_SIGNED_RSA_1), new HashMap<>()) - .asSecurityRequest(), - null - ); - - Assert.assertNotNull(creds); - assertThat(creds.getUsername(), is(TestJwts.MCCOY_SUBJECT)); - assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); - assertThat(creds.getBackendRoles().size(), is(0)); - assertThat(creds.getAttributes().size(), is(4)); - } - -} diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPOpenIdAuthenticatorTests.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPOpenIdAuthenticatorTests.java new file mode 100644 index 0000000000..9d66a99aea --- /dev/null +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/HTTPOpenIdAuthenticatorTests.java @@ -0,0 +1,693 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package com.amazon.dlic.auth.http.jwt.keybyoidc; + +import java.util.HashMap; +import java.util.List; + +import com.google.common.collect.ImmutableMap; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.opensearch.OpenSearchSecurityException; +import org.opensearch.common.settings.Settings; +import org.opensearch.security.user.AuthCredentials; +import org.opensearch.security.util.FakeRestRequest; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.APPLICATION_JWT; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.CLIENT_ID; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.ISSUER_ID_URL; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.MCCOY_SUBJECT; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.OIDC_TEST_AUD; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.OIDC_TEST_ISS; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.ROLES_CLAIM; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; + +public class HTTPOpenIdAuthenticatorTests { + + protected static MockIpdServer mockIdpServer; + + @BeforeClass + public static void setUp() throws Exception { + mockIdpServer = new MockIpdServer(TestJwk.Jwks.ALL); + } + + @AfterClass + public static void tearDown() { + if (mockIdpServer != null) { + try { + mockIdpServer.close(); + } catch (Exception e) {} + } + } + + @Test + public void basicTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void jwksUriTest() throws Exception { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void jwksMissingRequiredIssuerInClaimTest() throws Exception { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_ISSUER_OCT_1), new HashMap<>()) + .asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksNotMatchingRequiredIssuerInClaimTest() throws Exception { + Settings settings = Settings.builder().put("jwks_uri", mockIdpServer.getJwksUri()).put("required_issuer", "Wrong Issuer").build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksMatchAtLeastOneRequiredAudienceInClaimTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE + ",another_audience") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void jwksMissingRequiredAudienceInClaimTest() throws Exception { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_NO_AUDIENCE_OCT_1), new HashMap<>()) + .asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksNotMatchingRequiredAudienceInClaimTest() throws Exception { + Settings settings = Settings.builder() + .put("jwks_uri", mockIdpServer.getJwksUri()) + .put("required_audience", "Wrong Audience") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_2), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void jwksUriMissingTest() { + var exception = Assert.assertThrows(Exception.class, () -> { + HTTPOpenIdAuthenticator jwtAuth = new HTTPOpenIdAuthenticator(Settings.builder().build(), null); + jwtAuth.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap<>()).asSecurityRequest(), + null + ); + }); + + assertThat(exception.getMessage(), is("Authentication backend failed")); + assertThat(exception.getClass(), is(OpenSearchSecurityException.class)); + } + + @Test + public void testEscapeKid() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1_INVALID_KID), + new HashMap() + ).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void bearerTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()) + .asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void testRoles() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("roles_key", ROLES_CLAIM) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1), new HashMap()) + .asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getBackendRoles(), is(TestJwts.TEST_ROLES)); + } + + @Test + public void testExp() throws Exception { + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_EXPIRED_SIGNED_OCT_1), new HashMap<>()) + .asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void testExpInSkew() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "10") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + long expiringDate = System.currentTimeMillis() / 1000 - 5; + long notBeforeDate = System.currentTimeMillis() / 1000 - 25; + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap<>() + ).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + } + + @Test + public void testNbf() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "0") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + long expiringDate = 20 + System.currentTimeMillis() / 1000; + long notBeforeDate = 5 + System.currentTimeMillis() / 1000; + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap<>() + ).asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void testNbfInSkew() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("jwt_clock_skew_tolerance_seconds", "10") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + long expiringDate = 20 + System.currentTimeMillis() / 1000; + long notBeforeDate = 5 + System.currentTimeMillis() / 1000; + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.createMcCoySignedOct1(notBeforeDate, expiringDate)), + new HashMap<>() + ).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + } + + @Test + public void testRS256() throws Exception { + + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void testBadSignature() throws Exception { + + Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_X), new HashMap<>()).asSecurityRequest(), + null + ); + + Assert.assertNull(creds); + } + + @Test + public void testPeculiarJsonEscaping() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.PeculiarEscaping.MC_COY_SIGNED_RSA_1), new HashMap<>()) + .asSecurityRequest(), + null + ); + + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getAttributes().get("attr.jwt.aud"), is(List.of(TestJwts.TEST_AUDIENCE).toString())); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void userinfoEndpointReturnsJwtWithAllRequirementsTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoSignedUri()) + .put(CLIENT_ID, OIDC_TEST_AUD) + .put(ISSUER_ID_URL, OIDC_TEST_ISS) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = spy(new HTTPOpenIdAuthenticator(settings, null)); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void userinfoEndpointReturnsJwtWithRequiredAudIssFailsTest() throws Exception { // Setting a required issuer or audience + // alongside userinfo endpoint settings causes + // failures in signed response cases + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoSignedUri()) + .put(CLIENT_ID, OIDC_TEST_AUD) + .put(ISSUER_ID_URL, OIDC_TEST_ISS) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = null; + String message = ""; + try { + creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + } catch (RuntimeException e) { + message = e.getMessage(); + } + Assert.assertNull(creds); + assertTrue(message.contains("JWT audience rejected")); + } + + @Test + public void userinfoEndpointReturnsJwtWithMatchingRequiredAudIssPassesTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoSignedUri()) + .put(CLIENT_ID, OIDC_TEST_AUD) + .put(ISSUER_ID_URL, OIDC_TEST_ISS) + .put("required_issuer", OIDC_TEST_ISS) + .put("required_audience", OIDC_TEST_AUD) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1_OIDC, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(4)); + } + + @Test + public void userinfoEndpointReturnsJwtMissingIssuerTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoSignedUri()) + .put(CLIENT_ID, OIDC_TEST_AUD) + .put(ISSUER_ID_URL, "http://www.differentexample.com") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = null; + String message = ""; + try { + creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + } catch (AuthenticatorUnavailableException e) { + message = e.getMessage(); + } + Assert.assertNull(creds); + assertTrue(message.contains("Missing or invalid required claims in response: iss")); + } + + @Test + public void userinfoEndpointReturnsJwtMissingAudienceTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoSignedUri()) + .put(CLIENT_ID, "aDifferentTestClient") + .put(ISSUER_ID_URL, "http://www.example.com") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = null; + String message = ""; + try { + creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + } catch (AuthenticatorUnavailableException e) { + message = e.getMessage(); + } + Assert.assertNull(creds); + assertTrue(message.contains("Missing or invalid required claims in response: aud")); + } + + @Test + public void userinfoEndpointReturnsJwtMismatchedSubTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoSignedUri()) + .put(CLIENT_ID, "testClient") + .put(ISSUER_ID_URL, "http://www.example.com") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + AuthCredentials creds = null; + String message = ""; + try { + creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.STEPHEN_RSA_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + } catch (AuthenticatorUnavailableException e) { + message = e.getMessage(); + } + Assert.assertNull(creds); + assertTrue(message.contains("Missing or invalid required claims in response: sub")); + } + + @Test + public void userinfoEndpointReturnsJsonWithAllRequirementsTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoUri()) + .put(CLIENT_ID, "testClient") + .put(ISSUER_ID_URL, "http://www.example.com") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = spy(new HTTPOpenIdAuthenticator(settings, null)); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(2)); + } + + @Test + public void userinfoEndpointReturnsJsonMismatchedSubTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoUri()) + .put(CLIENT_ID, "testClient") + .put(ISSUER_ID_URL, "http://www.example.com") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + AuthCredentials creds = null; + String message = ""; + try { + creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.STEPHEN_RSA_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + } catch (AuthenticatorUnavailableException e) { + message = e.getMessage(); + } + Assert.assertNull(creds); + assertTrue(message.contains("Missing or invalid required claims in response: sub")); + } + + @Test + public void userinfoEndpointReturnsResponseNot2xxTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoUri()) + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE + ",another_audience") + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + AuthCredentials creds = null; + String message = ""; + try { + creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + } catch (AuthenticatorUnavailableException e) { + message = e.getMessage(); + } + Assert.assertNull(creds); + assertTrue(message.contains("Error while getting")); + } + + @Test + public void userinfoEndpointReturnsJsonWithRequiredAudIssPassesTest() throws Exception { + Settings settings = Settings.builder() + .put("openid_connect_url", mockIdpServer.getDiscoverUri()) + .put("userinfo_endpoint", mockIdpServer.getUserinfoUri()) + .put(CLIENT_ID, "testClient") + .put(ISSUER_ID_URL, "http://www.example.com") + .put("required_issuer", TestJwts.TEST_ISSUER) + .put("required_audience", TestJwts.TEST_AUDIENCE) + .build(); + + HTTPOpenIdAuthenticator openIdAuthenticator = new HTTPOpenIdAuthenticator(settings, null); + + AuthCredentials creds = openIdAuthenticator.extractCredentials( + new FakeRestRequest( + ImmutableMap.of("Authorization", "Bearer " + TestJwts.MC_COY_SIGNED_OCT_1, "Content-Type", APPLICATION_JWT), + new HashMap<>() + ).asSecurityRequest(), + null + ); + Assert.assertNotNull(creds); + assertThat(creds.getUsername(), is(MCCOY_SUBJECT)); + assertThat(creds.getBackendRoles().size(), is(0)); + assertThat(creds.getAttributes().size(), is(2)); + } +} diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java index 6b4fb9ef81..3bcbfa7d2d 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/MockIpdServer.java @@ -29,6 +29,7 @@ import javax.net.ssl.SSLSocket; import javax.net.ssl.TrustManagerFactory; +import org.apache.http.Header; import org.apache.http.HttpConnectionFactory; import org.apache.http.HttpException; import org.apache.http.HttpRequest; @@ -51,10 +52,19 @@ import org.opensearch.security.test.helper.network.SocketUtils; import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jwt.JWTClaimsSet; + +import static org.apache.http.entity.ContentType.APPLICATION_JSON; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.OpenIdConstants.APPLICATION_JWT; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.MCCOY_SUBJECT; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.TEST_ROLES_STRING; +import static com.amazon.dlic.auth.http.jwt.keybyoidc.TestJwts.createSigned; class MockIpdServer implements Closeable { final static String CTX_DISCOVER = "/discover"; final static String CTX_KEYS = "/api/oauth/keys"; + final static String CTX_USERINFO_SIGNED = "/api/oauth/userinfo/signed"; + final static String CTX_USERINFO = "/api/oauth/userinfo"; private final HttpServer httpServer; private final int port; @@ -91,6 +101,19 @@ public void handle(HttpRequest request, HttpResponse response, HttpContext conte handleKeysRequest(request, response, context); } + }) + .registerHandler(CTX_USERINFO, new HttpRequestHandler() { + + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { + handleUserinfoRequest(request, response, context); + } + }) + .registerHandler(CTX_USERINFO_SIGNED, new HttpRequestHandler() { + @Override + public void handle(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { + handleUserinfoRequestSigned(request, response, context); + } }); if (ssl) { @@ -149,6 +172,14 @@ public String getJwksUri() { return uri + CTX_KEYS; } + public String getUserinfoUri() { + return uri + CTX_USERINFO; + } + + public String getUserinfoSignedUri() { + return uri + CTX_USERINFO_SIGNED; + } + public int getPort() { return port; } @@ -164,6 +195,54 @@ protected void handleDiscoverRequest(HttpRequest request, HttpResponse response, ); } + protected void handleUserinfoRequestSigned(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, + IOException { + + Header headers = request.getFirstHeader("Authorization"); + String requestToken; + + String authHeaderValue = headers.getValue(); + if (authHeaderValue.startsWith("Bearer")) { + requestToken = authHeaderValue.substring(7).trim(); + } else { + response.setStatusCode(401); + return; + } + + response.setStatusCode(200); + response.setHeader("content-type", APPLICATION_JWT); + + // We have to manually form the response content since we don't want to need to pass settings info into the test class + JWTClaimsSet claims = new JWTClaimsSet.Builder().claim("sub", MCCOY_SUBJECT) + .claim("roles", TEST_ROLES_STRING) + .claim("iss", "http://www.example.com") + .claim("aud", "testClient") + .build(); + String content = createSigned(claims, TestJwk.OCT_1); + response.setEntity(new StringEntity(content)); + } + + protected void handleUserinfoRequest(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, + IOException { + Header headers = request.getFirstHeader("Authorization"); + String requestToken; + + String authHeaderValue = headers.getValue(); + if (authHeaderValue.startsWith("Bearer")) { + requestToken = authHeaderValue.substring(7).trim(); + } else { + response.setStatusCode(401); + return; + } + + response.setStatusCode(200); + response.setHeader("content-type", APPLICATION_JSON.getMimeType()); + + // We have to manually form the response content since we don't want to need to pass settings info into the test class + JWTClaimsSet claims = new JWTClaimsSet.Builder().claim("sub", MCCOY_SUBJECT).claim("roles", TEST_ROLES_STRING).build(); + response.setEntity(new StringEntity(claims.toString())); + } + protected void handleKeysRequest(HttpRequest request, HttpResponse response, HttpContext context) throws HttpException, IOException { response.setStatusCode(200); response.setEntity(new StringEntity(jwks.toString(false))); diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java index 6e3548926e..0d57ac6a01 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/SingleKeyHTTPJwtKeyByOpenIdConnectAuthenticatorTest.java @@ -33,7 +33,7 @@ public void basicTest() throws Exception { try { Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + HTTPOpenIdAuthenticator jwtAuth = new HTTPOpenIdAuthenticator(settings, null); AuthCredentials creds = jwtAuth.extractCredentials( new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap()) @@ -60,7 +60,7 @@ public void wrongSigTest() throws Exception { try { Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + HTTPOpenIdAuthenticator jwtAuth = new HTTPOpenIdAuthenticator(settings, null); AuthCredentials creds = jwtAuth.extractCredentials( new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_X), new HashMap()) @@ -83,7 +83,7 @@ public void noAlgTest() throws Exception { try { Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + HTTPOpenIdAuthenticator jwtAuth = new HTTPOpenIdAuthenticator(settings, null); AuthCredentials creds = jwtAuth.extractCredentials( new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.MC_COY_SIGNED_RSA_1), new HashMap()) @@ -109,7 +109,7 @@ public void mismatchedAlgTest() throws Exception { try { Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + HTTPOpenIdAuthenticator jwtAuth = new HTTPOpenIdAuthenticator(settings, null); AuthCredentials creds = jwtAuth.extractCredentials( new FakeRestRequest(ImmutableMap.of("Authorization", TestJwts.NoKid.MC_COY_SIGNED_RSA_1), new HashMap()) @@ -132,7 +132,7 @@ public void keyExchangeTest() throws Exception { Settings settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); - HTTPJwtKeyByOpenIdConnectAuthenticator jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + HTTPOpenIdAuthenticator jwtAuth = new HTTPOpenIdAuthenticator(settings, null); try { AuthCredentials creds = jwtAuth.extractCredentials( @@ -183,7 +183,7 @@ public void keyExchangeTest() throws Exception { mockIdpServer = new MockIpdServer(TestJwk.Jwks.RSA_2); settings = Settings.builder().put("openid_connect_url", mockIdpServer.getDiscoverUri()).build(); // port changed - jwtAuth = new HTTPJwtKeyByOpenIdConnectAuthenticator(settings, null); + jwtAuth = new HTTPOpenIdAuthenticator(settings, null); try { AuthCredentials creds = jwtAuth.extractCredentials( diff --git a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java index 4a6d5f97e9..6cce145876 100644 --- a/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java +++ b/src/test/java/com/amazon/dlic/auth/http/jwt/keybyoidc/TestJwts.java @@ -35,12 +35,22 @@ class TestJwts { static final String TEST_AUDIENCE = "TestAudience"; + static final String OIDC_TEST_AUD = "testClient"; + + static final String OIDC_TEST_ISS = "http://www.example.com"; + + static final String STEPHEN_SUBJECT = "Stephen Crawford"; + static final String MCCOY_SUBJECT = "Leonard McCoy"; static final String TEST_ISSUER = "TestIssuer"; + static final JWTClaimsSet STEPHEN = create(STEPHEN_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + static final JWTClaimsSet MC_COY = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); + static final JWTClaimsSet MC_COY_OIDC = create(MCCOY_SUBJECT, OIDC_TEST_AUD, OIDC_TEST_ISS, ROLES_CLAIM, TEST_ROLES_STRING); + static final JWTClaimsSet MC_COY_2 = create(MCCOY_SUBJECT, TEST_AUDIENCE, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); static final JWTClaimsSet MC_COY_NO_AUDIENCE = create(MCCOY_SUBJECT, null, TEST_ISSUER, ROLES_CLAIM, TEST_ROLES_STRING); @@ -59,6 +69,8 @@ class TestJwts { static final String MC_COY_SIGNED_OCT_1 = createSigned(MC_COY, TestJwk.OCT_1); + static final String MC_COY_SIGNED_OCT_1_OIDC = createSigned(MC_COY_OIDC, TestJwk.OCT_1); + static final String MC_COY_SIGNED_OCT_2 = createSigned(MC_COY_2, TestJwk.OCT_2); static final String MC_COY_SIGNED_NO_AUDIENCE_OCT_1 = createSigned(MC_COY_NO_AUDIENCE, TestJwk.OCT_1); @@ -68,6 +80,8 @@ class TestJwts { static final String MC_COY_SIGNED_RSA_1 = createSigned(MC_COY, TestJwk.RSA_1); + static final String STEPHEN_RSA_1 = createSigned(STEPHEN, TestJwk.RSA_1); + static final String MC_COY_SIGNED_RSA_X = createSigned(MC_COY, TestJwk.RSA_X); static final String MC_COY_EXPIRED_SIGNED_OCT_1 = createSigned(MC_COY_EXPIRED, TestJwk.OCT_1);