diff --git a/sandbox/libs/authn/src/main/java/org/opensearch/authn/AuthenticationTokenHandler.java b/sandbox/libs/authn/src/main/java/org/opensearch/authn/AuthenticationTokenHandler.java index 12840c3ee8006..4a4cb6f061780 100644 --- a/sandbox/libs/authn/src/main/java/org/opensearch/authn/AuthenticationTokenHandler.java +++ b/sandbox/libs/authn/src/main/java/org/opensearch/authn/AuthenticationTokenHandler.java @@ -48,13 +48,29 @@ private static AuthenticationToken handleBasicAuth(final BasicAuthToken token) { final byte[] decodedAuthHeader = Base64.getDecoder().decode(token.getHeaderValue().substring("Basic".length()).trim()); String decodedHeader = new String(decodedAuthHeader, StandardCharsets.UTF_8); - final String[] decodedUserNamePassword = decodedHeader.split(":"); + final int firstColonIndex = decodedHeader.indexOf(':'); - // Malformed AuthHeader strings - if (decodedUserNamePassword.length != 2) return null; + String username = null; + String password = null; - logger.info("Logging in as: " + decodedUserNamePassword[0]); + if (firstColonIndex > 0) { + username = decodedHeader.substring(0, firstColonIndex); - return new UsernamePasswordToken(decodedUserNamePassword[0], decodedUserNamePassword[1]); + if (decodedHeader.length() - 1 != firstColonIndex) { + password = decodedHeader.substring(firstColonIndex + 1); + } else { + // blank password + password = ""; + } + } + + if (username == null || password == null) { + logger.warn("Invalid 'Authorization' header, send 401 and 'WWW-Authenticate Basic'"); + return null; + } + + logger.info("Logging in as: " + username); + + return new UsernamePasswordToken(username, password); } } diff --git a/sandbox/libs/authn/src/main/resources/example/example_internal_users.yml b/sandbox/libs/authn/src/main/resources/example/example_internal_users.yml index 96453a6aed8f5..0c66535211737 100644 --- a/sandbox/libs/authn/src/main/resources/example/example_internal_users.yml +++ b/sandbox/libs/authn/src/main/resources/example/example_internal_users.yml @@ -30,3 +30,7 @@ readall: snapshotrestore: hash: "$2y$12$DpwmetHKwgYnorbgdvORCenv4NAK8cPUg8AI6pxLCuWf/ALc0.v7W" + +# Two semi-colon password +test: + hash: "$2y$12$fG1vNbK3X73j9eujLfh6We43fbKdy8O8RP5tGnLjg/CWMot48kAwO" # te:st diff --git a/sandbox/libs/authn/src/test/java/org/opensearch/authn/AuthenticationTokenHandlerTests.java b/sandbox/libs/authn/src/test/java/org/opensearch/authn/AuthenticationTokenHandlerTests.java index ca67b7f0b2633..03dcf6b26a53f 100644 --- a/sandbox/libs/authn/src/test/java/org/opensearch/authn/AuthenticationTokenHandlerTests.java +++ b/sandbox/libs/authn/src/test/java/org/opensearch/authn/AuthenticationTokenHandlerTests.java @@ -11,6 +11,7 @@ import org.opensearch.authn.tokens.BasicAuthToken; import org.opensearch.test.OpenSearchTestCase; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; @@ -26,6 +27,22 @@ public void testShouldExtractBasicAuthTokenSuccessfully() { UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) AuthenticationTokenHandler.extractShiroAuthToken(authToken); MatcherAssert.assertThat(usernamePasswordToken, notNullValue()); + MatcherAssert.assertThat(usernamePasswordToken.getUsername(), equalTo("admin")); + MatcherAssert.assertThat(new String(usernamePasswordToken.getPassword()), equalTo("admin")); + } + + public void testShouldExtractBasicAuthTokenSuccessfully_twoSemiColonPassword() { + + // The auth header that is part of the request + String authHeader = "Basic dGVzdDp0ZTpzdA=="; // test:te:st + + AuthenticationToken authToken = new BasicAuthToken(authHeader); + + UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) AuthenticationTokenHandler.extractShiroAuthToken(authToken); + + MatcherAssert.assertThat(usernamePasswordToken, notNullValue()); + MatcherAssert.assertThat(usernamePasswordToken.getUsername(), equalTo("test")); + MatcherAssert.assertThat(new String(usernamePasswordToken.getPassword()), equalTo("te:st")); } public void testShouldReturnNullWhenExtractingInvalidToken() { diff --git a/sandbox/libs/authn/src/test/java/org/opensearch/authn/type/BasicAuthenticationTests.java b/sandbox/libs/authn/src/test/java/org/opensearch/authn/type/BasicAuthenticationTests.java index 220f66a55885b..8d71686146f52 100644 --- a/sandbox/libs/authn/src/test/java/org/opensearch/authn/type/BasicAuthenticationTests.java +++ b/sandbox/libs/authn/src/test/java/org/opensearch/authn/type/BasicAuthenticationTests.java @@ -32,6 +32,20 @@ public void testClusterHealthWithValidAuthenticationHeader() throws IOException } + public void testClusterHealthWithValidAuthenticationHeader_twoSemiColonPassword() throws IOException { + Request request = new Request("GET", "/_cluster/health"); + RequestOptions options = RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Basic dGVzdDp0ZTpzdA==").build(); // test:te:st + request.setOptions(options); + Response response = client().performRequest(request); + + assertOK(response); + + // Standard cluster health response + MatcherAssert.assertThat(entityAsMap(response).size(), equalTo(17)); + MatcherAssert.assertThat(entityAsMap(response).get("status"), equalTo("green")); + + } + public void testClusterHealthWithNoHeader() throws IOException { Request request = new Request("GET", "/_cluster/health"); RequestOptions options = RequestOptions.DEFAULT.toBuilder().build(); // admin:admin @@ -65,8 +79,7 @@ public void testClusterHealthWithInvalidAuthenticationHeader() throws IOExceptio public void testClusterHealthWithCorruptAuthenticationHeader() throws IOException { Request request = new Request("GET", "/_cluster/health"); - RequestOptions options = RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Basic bleh").build(); // marvin:galaxy - request.setOptions(options); + RequestOptions options = RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "Basic bleh").build(); try { client().performRequest(request); } catch (ResponseException e) {