From a73d42f409bee78862220c524b040a9276cb02ac Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Fri, 5 May 2023 16:14:58 -0400 Subject: [PATCH 01/21] TokenManager Interface Signed-off-by: Stephen Crawford --- .../identity/shiro/AuthTokenHandler.java | 37 -------- .../identity/shiro/ShiroIdentityPlugin.java | 4 +- .../identity/shiro/ShiroSubject.java | 4 +- .../identity/shiro/ShiroTokenHandler.java | 94 +++++++++++++++++++ .../identity/shiro/AuthTokenHandlerTests.java | 36 ++++++- .../identity/shiro/ShiroSubjectTests.java | 4 +- .../identity/noop/NoopTokenManager.java | 43 +++++++++ .../identity/tokens/BasicAuthToken.java | 14 ++- .../opensearch/identity/tokens/NoopToken.java | 18 ++++ .../identity/tokens/RestTokenExtractor.java | 2 +- .../identity/tokens/TokenManager.java | 22 +++++ 11 files changed, 230 insertions(+), 48 deletions(-) delete mode 100644 plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/AuthTokenHandler.java create mode 100644 plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java create mode 100644 server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java create mode 100644 server/src/main/java/org/opensearch/identity/tokens/NoopToken.java create mode 100644 server/src/main/java/org/opensearch/identity/tokens/TokenManager.java diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/AuthTokenHandler.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/AuthTokenHandler.java deleted file mode 100644 index 14801b665f14f..0000000000000 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/AuthTokenHandler.java +++ /dev/null @@ -1,37 +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. - */ - -package org.opensearch.identity.shiro; - -import java.util.Optional; - -import org.apache.shiro.authc.AuthenticationToken; -import org.apache.shiro.authc.UsernamePasswordToken; -import org.opensearch.identity.tokens.BasicAuthToken; - -/** - * Extracts Shiro's {@link AuthenticationToken} from different types of auth headers - * - * @opensearch.experimental - */ -class AuthTokenHandler { - - /** - * Translates into shiro auth token from the given header token - * @param authenticationToken the token from which to translate - * @return An optional of the shiro auth token for login - */ - public Optional translateAuthToken(org.opensearch.identity.tokens.AuthToken authenticationToken) { - if (authenticationToken instanceof BasicAuthToken) { - final BasicAuthToken basicAuthToken = (BasicAuthToken) authenticationToken; - return Optional.of(new UsernamePasswordToken(basicAuthToken.getUser(), basicAuthToken.getPassword())); - } - - return Optional.empty(); - } -} diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java index eee5dd8ce0bd4..3cfc2fffe9f48 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java @@ -26,7 +26,7 @@ public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin private Logger log = LogManager.getLogger(this.getClass()); private final Settings settings; - private final AuthTokenHandler authTokenHandler; + private final ShiroTokenHandler authTokenHandler; /** * Create a new instance of the Shiro Identity Plugin @@ -35,7 +35,7 @@ public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin */ public ShiroIdentityPlugin(final Settings settings) { this.settings = settings; - authTokenHandler = new AuthTokenHandler(); + authTokenHandler = new ShiroTokenHandler(); SecurityManager securityManager = new ShiroSecurityManager(); SecurityUtils.setSecurityManager(securityManager); diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java index 3208d2bb5d8ca..414e2aa921d29 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java @@ -20,7 +20,7 @@ * @opensearch.experimental */ public class ShiroSubject implements Subject { - private final AuthTokenHandler authTokenHandler; + private final ShiroTokenHandler authTokenHandler; private final org.apache.shiro.subject.Subject shiroSubject; /** @@ -29,7 +29,7 @@ public class ShiroSubject implements Subject { * @param authTokenHandler Used to extract auth header info * @param subject The specific subject being authc/z'd */ - public ShiroSubject(final AuthTokenHandler authTokenHandler, final org.apache.shiro.subject.Subject subject) { + public ShiroSubject(final ShiroTokenHandler authTokenHandler, final org.apache.shiro.subject.Subject subject) { this.authTokenHandler = Objects.requireNonNull(authTokenHandler); this.shiroSubject = Objects.requireNonNull(subject); } diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java new file mode 100644 index 0000000000000..95fdad904eca2 --- /dev/null +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java @@ -0,0 +1,94 @@ +/* + * 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. + */ + +package org.opensearch.identity.shiro; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Optional; + +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.authc.AuthenticationToken; +import org.apache.shiro.authc.UsernamePasswordToken; +import org.opensearch.identity.Subject; +import org.opensearch.identity.tokens.AuthToken; +import org.opensearch.identity.tokens.BasicAuthToken; +import org.opensearch.identity.tokens.TokenManager; + +/** + * Extracts Shiro's {@link AuthenticationToken} from different types of auth headers + * + * @opensearch.experimental + */ +class ShiroTokenHandler implements TokenManager { + + /** + * Translates into shiro auth token from the given header token + * @param authenticationToken the token from which to translate + * @return An optional of the shiro auth token for login + */ + public Optional translateAuthToken(org.opensearch.identity.tokens.AuthToken authenticationToken) { + if (authenticationToken instanceof BasicAuthToken) { + final BasicAuthToken basicAuthToken = (BasicAuthToken) authenticationToken; + return Optional.of(new UsernamePasswordToken(basicAuthToken.getUser(), basicAuthToken.getPassword())); + } + + return Optional.empty(); + } + + @Override + public AuthToken generateToken() { + + Subject subject = new ShiroSubject(this, SecurityUtils.getSubject()); + final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + generatePassword()).getBytes()); + final String usernamePassword = new String(rawEncoded, StandardCharsets.UTF_8); + final String header = "Basic " + usernamePassword; + + return new BasicAuthToken(header); + } + + @Override + public boolean validateToken(AuthToken token) { + if (token instanceof BasicAuthToken) { + final BasicAuthToken basicAuthToken = (BasicAuthToken) token; + if (basicAuthToken.getUser().equals(SecurityUtils.getSubject()) && basicAuthToken.getPassword().equals(generatePassword())) { + return true; + } + } + return false; + } + + @Override + public String getTokenInfo(AuthToken token) { + if (token instanceof BasicAuthToken) { + final BasicAuthToken basicAuthToken = (BasicAuthToken) token; + return basicAuthToken.toString(); + } + throw new UnsupportedAuthenticationToken(); + } + + @Override + public void revokeToken(AuthToken token) { + if (token instanceof BasicAuthToken) { + final BasicAuthToken basicAuthToken = (BasicAuthToken) token; + basicAuthToken.revoke(); + return; + } + throw new UnsupportedAuthenticationToken(); + } + + @Override + public void refreshToken(AuthToken token) { + + } + + public String generatePassword() { + return "superSecurePassword1!"; + } + +} diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 942d777df2086..0bc198ce45ce6 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -10,7 +10,9 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; +import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; +import org.opensearch.identity.tokens.NoopToken; import org.opensearch.test.OpenSearchTestCase; import org.junit.Before; @@ -23,11 +25,11 @@ public class AuthTokenHandlerTests extends OpenSearchTestCase { - private AuthTokenHandler authTokenHandler; + private ShiroTokenHandler authTokenHandler; @Before public void testSetup() { - authTokenHandler = new AuthTokenHandler(); + authTokenHandler = new ShiroTokenHandler(); } public void testShouldExtractBasicAuthTokenSuccessfully() { @@ -59,4 +61,34 @@ public void testShouldReturnNullWhenExtractingNullToken() { assertThat(translatedToken.isEmpty(), is(true)); } + + public void testShouldRevokeTokenSuccessfully() { + final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); + authTokenHandler.revokeToken(authToken); + assert(authToken.toString().equals("Basic auth token with user=, password=")); + } + + public void testShouldFailWhenRevokeToken() { + final NoopToken authToken = new NoopToken(); + assert(authToken.getTokenIdentifier().equals("Noop")); + assertThrows(UnsupportedAuthenticationToken.class, () -> authTokenHandler.revokeToken(authToken)); + } + + public void testShouldGetTokenInfoSuccessfully() { + final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + assert(authToken.toString().equals(authTokenHandler.getTokenInfo(authToken))); + } + + public void testShouldFailGetTokenInfo() { + final NoopToken authToken = new NoopToken(); + assert(authToken.getTokenIdentifier().equals("Noop")); + assertThrows(UnsupportedAuthenticationToken.class, () -> authTokenHandler.getTokenInfo(authToken)); + } + + + public void testShouldFailValidateToken() { + final AuthToken authToken = new NoopToken(); + assertFalse(authTokenHandler.validateToken(authToken)); + } } diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java index baf6090f79ff3..311df738108c6 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java @@ -25,13 +25,13 @@ public class ShiroSubjectTests extends OpenSearchTestCase { private org.apache.shiro.subject.Subject shiroSubject; - private AuthTokenHandler authTokenHandler; + private ShiroTokenHandler authTokenHandler; private ShiroSubject subject; @Before public void setup() { shiroSubject = mock(org.apache.shiro.subject.Subject.class); - authTokenHandler = mock(AuthTokenHandler.class); + authTokenHandler = mock(ShiroTokenHandler.class); subject = new ShiroSubject(authTokenHandler, shiroSubject); } diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java new file mode 100644 index 0000000000000..f8bb009a30175 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package org.opensearch.identity.noop; + +import org.opensearch.identity.tokens.AuthToken; +import org.opensearch.identity.tokens.NoopToken; +import org.opensearch.identity.tokens.TokenManager; + +public class NoopTokenManager implements TokenManager { + @Override + public AuthToken generateToken() { + return new NoopToken(); + } + + @Override + public boolean validateToken(AuthToken token) { + if (token instanceof NoopToken){ + return true; + } + return false; + } + + @Override + public String getTokenInfo(AuthToken token) { + return "Token is NoopToken"; + } + + @Override + public void revokeToken(AuthToken token) { + + } + + @Override + public void refreshToken(AuthToken token) { + + } +} diff --git a/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java index e5000cb3d6965..59db713bb4f0e 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java @@ -16,13 +16,13 @@ */ public final class BasicAuthToken implements AuthToken { - public final static String TOKEN_IDENIFIER = "Basic"; + public final static String TOKEN_IDENTIFIER = "Basic"; private String user; private String password; public BasicAuthToken(final String headerValue) { - final String base64Encoded = headerValue.substring(TOKEN_IDENIFIER.length()).trim(); + final String base64Encoded = headerValue.substring(TOKEN_IDENTIFIER.length()).trim(); final byte[] rawDecoded = Base64.getDecoder().decode(base64Encoded); final String usernamepassword = new String(rawDecoded, StandardCharsets.UTF_8); @@ -41,4 +41,14 @@ public String getUser() { public String getPassword() { return password; } + + @Override + public String toString(){ + return "Basic auth token with user=" + user + ", password=" + password; + } + + public void revoke() { + this.password=""; + this.user=""; + } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java b/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java new file mode 100644 index 0000000000000..c566d350c1446 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java @@ -0,0 +1,18 @@ +/* + * 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. + */ + +package org.opensearch.identity.tokens; + +public class NoopToken implements AuthToken { + public final static String TOKEN_IDENTIFIER = "Noop"; + + public String getTokenIdentifier() { + return TOKEN_IDENTIFIER; + } + +} diff --git a/server/src/main/java/org/opensearch/identity/tokens/RestTokenExtractor.java b/server/src/main/java/org/opensearch/identity/tokens/RestTokenExtractor.java index eaeacdb240fd9..ae200c7461a60 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/RestTokenExtractor.java +++ b/server/src/main/java/org/opensearch/identity/tokens/RestTokenExtractor.java @@ -40,7 +40,7 @@ public static AuthToken extractToken(final RestRequest request) { if (authHeaderValue.isPresent()) { final String authHeaderValueStr = authHeaderValue.get(); - if (authHeaderValueStr.startsWith(BasicAuthToken.TOKEN_IDENIFIER)) { + if (authHeaderValueStr.startsWith(BasicAuthToken.TOKEN_IDENTIFIER)) { return new BasicAuthToken(authHeaderValueStr); } else { if (logger.isDebugEnabled()) { diff --git a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java new file mode 100644 index 0000000000000..00cf7adf12737 --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java @@ -0,0 +1,22 @@ +/* + * 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. + */ + +package org.opensearch.identity.tokens; + +public interface TokenManager { + + public AuthToken generateToken(); + + public boolean validateToken(AuthToken token); + + public String getTokenInfo(AuthToken token); + + public void revokeToken(AuthToken token); + + public void refreshToken(AuthToken token); +} From 6cd03531a7abbbcd68875f4fa48093acb0d54241 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Mon, 8 May 2023 10:58:58 -0400 Subject: [PATCH 02/21] Update javadocs Signed-off-by: Stephen Crawford --- .../identity/noop/NoopTokenManager.java | 31 ++++++++++++++++++- .../opensearch/identity/tokens/NoopToken.java | 7 +++++ .../identity/tokens/TokenManager.java | 25 +++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java index f8bb009a30175..96dac1e045372 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java @@ -12,12 +12,25 @@ import org.opensearch.identity.tokens.NoopToken; import org.opensearch.identity.tokens.TokenManager; +/** + * This class represents a Noop Token Manager + */ public class NoopTokenManager implements TokenManager { + + /** + * Generate a new Noop Token + * @return a new Noop Token + */ @Override public AuthToken generateToken() { return new NoopToken(); } + /** + * Validate a token + * @param token The token to be validated + * @return If the token is a Noop Token, then pass with True; otherwise fail with False. + */ @Override public boolean validateToken(AuthToken token) { if (token instanceof NoopToken){ @@ -26,16 +39,32 @@ public boolean validateToken(AuthToken token) { return false; } + /** + * Get token info, there should not be any token info so just return whether the token is a NoopToken + * @param token The auth token to be parsed + * @return A String stating the token is a NoopToken or is not a NopToken + */ @Override public String getTokenInfo(AuthToken token) { - return "Token is NoopToken"; + if (token instanceof NoopToken){ + return "Token is NoopToken"; + } + return "Token is not a NoopToken"; } + /** + * Revoking a Noop Token should not do anything + * @param token The Auth Token to be revoked + */ @Override public void revokeToken(AuthToken token) { } + /** + * Refreshing a NoopToken also not do anything + * @param token The token to be refreshed + */ @Override public void refreshToken(AuthToken token) { diff --git a/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java b/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java index c566d350c1446..2e19c740672ce 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java @@ -8,9 +8,16 @@ package org.opensearch.identity.tokens; +/** + * A NoopToken is a pass-through AuthToken + */ public class NoopToken implements AuthToken { public final static String TOKEN_IDENTIFIER = "Noop"; + /** + * Returns the TokenIdentifier of Noop + * @return The token identifier "Noop" + */ public String getTokenIdentifier() { return TOKEN_IDENTIFIER; } diff --git a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java index 00cf7adf12737..faed3ba738540 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java +++ b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java @@ -8,15 +8,40 @@ package org.opensearch.identity.tokens; +/** + * This interface defines the expected methods of a token manager + */ public interface TokenManager { + /** + * Create a new auth token + * @return A new auth token + */ public AuthToken generateToken(); + /** + * Validate an auth token based on the rules associated with its format + * @param token The token to validate + * @return True if the token is valid; False if the token is not valid + */ public boolean validateToken(AuthToken token); + /** + * Fetch the info from a token + * @param token The auth token to be parsed + * @return A String representing the info associated with the token + */ public String getTokenInfo(AuthToken token); + /** + * Revoke a token that should no longer be treated as valid + * @param token The Auth Token to be revoked + */ public void revokeToken(AuthToken token); + /** + * Updates a token to be valid for a greater period of time or to have different attributes. + * @param token The token to be refreshed + */ public void refreshToken(AuthToken token); } From ba38c92668956b2bc391c3bc425732046e79b766 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Mon, 15 May 2023 14:51:54 -0400 Subject: [PATCH 03/21] Update changelog Signed-off-by: Stephen Crawford --- CHANGELOG.md | 1 + .../org/opensearch/identity/shiro/ShiroTokenHandler.java | 8 ++++---- .../opensearch/identity/shiro/AuthTokenHandlerTests.java | 9 ++++----- .../org/opensearch/identity/noop/NoopTokenManager.java | 6 +++--- .../org/opensearch/identity/tokens/BasicAuthToken.java | 6 +++--- .../org/opensearch/identity/tokens/TokenManager.java | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6673b00e16ea..7b672e21372f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Add back primary shard preference for queries ([#7375](https://github.com/opensearch-project/OpenSearch/pull/7375)) - Add descending order search optimization through reverse segment read. ([#7244](https://github.com/opensearch-project/OpenSearch/pull/7244)) - Adds ExtensionsManager.lookupExtensionSettingsById ([#7466](https://github.com/opensearch-project/OpenSearch/pull/7466)) +- Add TokenManager Interface ([#7452](https://github.com/opensearch-project/OpenSearch/pull/7452)) ### Dependencies - Bump `com.netflix.nebula:gradle-info-plugin` from 12.0.0 to 12.1.0 diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java index 95fdad904eca2..797d062d761b2 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java @@ -8,7 +8,6 @@ package org.opensearch.identity.shiro; -import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Optional; @@ -19,6 +18,7 @@ import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.identity.tokens.TokenManager; +import static java.nio.charset.StandardCharsets.UTF_8; /** * Extracts Shiro's {@link AuthenticationToken} from different types of auth headers @@ -45,8 +45,8 @@ public Optional translateAuthToken(org.opensearch.identity. public AuthToken generateToken() { Subject subject = new ShiroSubject(this, SecurityUtils.getSubject()); - final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + generatePassword()).getBytes()); - final String usernamePassword = new String(rawEncoded, StandardCharsets.UTF_8); + final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + generatePassword()).getBytes(UTF_8)); + final String usernamePassword = new String(rawEncoded, UTF_8); final String header = "Basic " + usernamePassword; return new BasicAuthToken(header); @@ -83,7 +83,7 @@ public void revokeToken(AuthToken token) { } @Override - public void refreshToken(AuthToken token) { + public void resetToken(AuthToken token) { } diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 0bc198ce45ce6..b927c36ab22a8 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -66,27 +66,26 @@ public void testShouldRevokeTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); authTokenHandler.revokeToken(authToken); - assert(authToken.toString().equals("Basic auth token with user=, password=")); + assert (authToken.toString().equals("Basic auth token with user=, password=")); } public void testShouldFailWhenRevokeToken() { final NoopToken authToken = new NoopToken(); - assert(authToken.getTokenIdentifier().equals("Noop")); + assert (authToken.getTokenIdentifier().equals("Noop")); assertThrows(UnsupportedAuthenticationToken.class, () -> authTokenHandler.revokeToken(authToken)); } public void testShouldGetTokenInfoSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assert(authToken.toString().equals(authTokenHandler.getTokenInfo(authToken))); + assert (authToken.toString().equals(authTokenHandler.getTokenInfo(authToken))); } public void testShouldFailGetTokenInfo() { final NoopToken authToken = new NoopToken(); - assert(authToken.getTokenIdentifier().equals("Noop")); + assert (authToken.getTokenIdentifier().equals("Noop")); assertThrows(UnsupportedAuthenticationToken.class, () -> authTokenHandler.getTokenInfo(authToken)); } - public void testShouldFailValidateToken() { final AuthToken authToken = new NoopToken(); assertFalse(authTokenHandler.validateToken(authToken)); diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java index 96dac1e045372..6568dc6a00ae0 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java @@ -33,7 +33,7 @@ public AuthToken generateToken() { */ @Override public boolean validateToken(AuthToken token) { - if (token instanceof NoopToken){ + if (token instanceof NoopToken) { return true; } return false; @@ -46,7 +46,7 @@ public boolean validateToken(AuthToken token) { */ @Override public String getTokenInfo(AuthToken token) { - if (token instanceof NoopToken){ + if (token instanceof NoopToken) { return "Token is NoopToken"; } return "Token is not a NoopToken"; @@ -66,7 +66,7 @@ public void revokeToken(AuthToken token) { * @param token The token to be refreshed */ @Override - public void refreshToken(AuthToken token) { + public void resetToken(AuthToken token) { } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java index 59db713bb4f0e..9cd6cb6b6208a 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BasicAuthToken.java @@ -43,12 +43,12 @@ public String getPassword() { } @Override - public String toString(){ + public String toString() { return "Basic auth token with user=" + user + ", password=" + password; } public void revoke() { - this.password=""; - this.user=""; + this.password = ""; + this.user = ""; } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java index faed3ba738540..37b9f9156e2f7 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java +++ b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java @@ -43,5 +43,5 @@ public interface TokenManager { * Updates a token to be valid for a greater period of time or to have different attributes. * @param token The token to be refreshed */ - public void refreshToken(AuthToken token); + public void resetToken(AuthToken token); } From 3f2d228b5b7094bd8129856aa0fcfd02adff68b2 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 16 May 2023 11:08:58 -0400 Subject: [PATCH 04/21] Update code coverage Signed-off-by: Stephen Crawford --- .../identity/shiro/ShiroTokenHandler.java | 5 +- .../identity/shiro/AuthTokenHandlerTests.java | 46 +++++++++++++++---- ...okenManager.java => NoopTokenHandler.java} | 20 ++++++-- 3 files changed, 57 insertions(+), 14 deletions(-) rename server/src/main/java/org/opensearch/identity/noop/{NoopTokenManager.java => NoopTokenHandler.java} (70%) diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java index 797d062d761b2..e695e479c57cd 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java @@ -84,7 +84,10 @@ public void revokeToken(AuthToken token) { @Override public void resetToken(AuthToken token) { - + if (token instanceof BasicAuthToken) { + final BasicAuthToken basicAuthToken = (BasicAuthToken) token; + basicAuthToken.revoke(); + } } public String generatePassword() { diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index b927c36ab22a8..dc4e21b37b2ae 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -10,6 +10,8 @@ import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; +import org.opensearch.OpenSearchException; +import org.opensearch.identity.noop.NoopTokenHandler; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.identity.tokens.NoopToken; @@ -25,17 +27,19 @@ public class AuthTokenHandlerTests extends OpenSearchTestCase { - private ShiroTokenHandler authTokenHandler; + private ShiroTokenHandler shiroAuthTokenHandler; + private NoopTokenHandler noopTokenHandler; @Before public void testSetup() { - authTokenHandler = new ShiroTokenHandler(); + shiroAuthTokenHandler = new ShiroTokenHandler(); + noopTokenHandler = new NoopTokenHandler(); } public void testShouldExtractBasicAuthTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic YWRtaW46YWRtaW4="); // admin:admin - final AuthenticationToken translatedToken = authTokenHandler.translateAuthToken(authToken).get(); + final AuthenticationToken translatedToken = shiroAuthTokenHandler.translateAuthToken(authToken).get(); assertThat(translatedToken, is(instanceOf(UsernamePasswordToken.class))); final UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) translatedToken; @@ -47,7 +51,7 @@ public void testShouldExtractBasicAuthTokenSuccessfully() { public void testShouldExtractBasicAuthTokenSuccessfully_twoSemiColonPassword() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); // test:te:st - final AuthenticationToken translatedToken = authTokenHandler.translateAuthToken(authToken).get(); + final AuthenticationToken translatedToken = shiroAuthTokenHandler.translateAuthToken(authToken).get(); assertThat(translatedToken, is(instanceOf(UsernamePasswordToken.class))); final UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) translatedToken; @@ -57,7 +61,7 @@ public void testShouldExtractBasicAuthTokenSuccessfully_twoSemiColonPassword() { } public void testShouldReturnNullWhenExtractingNullToken() { - final Optional translatedToken = authTokenHandler.translateAuthToken(null); + final Optional translatedToken = shiroAuthTokenHandler.translateAuthToken(null); assertThat(translatedToken.isEmpty(), is(true)); } @@ -65,29 +69,51 @@ public void testShouldReturnNullWhenExtractingNullToken() { public void testShouldRevokeTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); - authTokenHandler.revokeToken(authToken); + shiroAuthTokenHandler.revokeToken(authToken); assert (authToken.toString().equals("Basic auth token with user=, password=")); } public void testShouldFailWhenRevokeToken() { final NoopToken authToken = new NoopToken(); assert (authToken.getTokenIdentifier().equals("Noop")); - assertThrows(UnsupportedAuthenticationToken.class, () -> authTokenHandler.revokeToken(authToken)); + assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.revokeToken(authToken)); } public void testShouldGetTokenInfoSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assert (authToken.toString().equals(authTokenHandler.getTokenInfo(authToken))); + assert (authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); + final NoopToken noopAuthToken = new NoopToken(); + assert (noopTokenHandler.getTokenInfo(noopAuthToken).equals("Token is NoopToken")); } public void testShouldFailGetTokenInfo() { final NoopToken authToken = new NoopToken(); assert (authToken.getTokenIdentifier().equals("Noop")); - assertThrows(UnsupportedAuthenticationToken.class, () -> authTokenHandler.getTokenInfo(authToken)); + assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.getTokenInfo(authToken)); } public void testShouldFailValidateToken() { final AuthToken authToken = new NoopToken(); - assertFalse(authTokenHandler.validateToken(authToken)); + assertFalse(shiroAuthTokenHandler.validateToken(authToken)); } + + public void testShouldResetToken(AuthToken token) { + BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + shiroAuthTokenHandler.resetToken(authToken); + assert (authToken.getPassword().equals("")); + assert (authToken.getUser().equals("")); + } + + public void testShouldPassThrough() { + final NoopToken authToken = new NoopToken(); + noopTokenHandler.resetToken(authToken); + noopTokenHandler.revokeToken(authToken); + } + + public void testShouldFailPassThrough() { + BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + assertThrows(OpenSearchException.class, () -> noopTokenHandler.resetToken(authToken)); + assertThrows(OpenSearchException.class, () -> noopTokenHandler.revokeToken(authToken)); + } + } diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java similarity index 70% rename from server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java rename to server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java index 6568dc6a00ae0..6dd1618d3e6c9 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java @@ -8,6 +8,10 @@ package org.opensearch.identity.noop; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.OpenSearchException; +import org.opensearch.identity.IdentityService; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.NoopToken; import org.opensearch.identity.tokens.TokenManager; @@ -15,7 +19,9 @@ /** * This class represents a Noop Token Manager */ -public class NoopTokenManager implements TokenManager { +public class NoopTokenHandler implements TokenManager { + + private static final Logger log = LogManager.getLogger(IdentityService.class); /** * Generate a new Noop Token @@ -58,7 +64,11 @@ public String getTokenInfo(AuthToken token) { */ @Override public void revokeToken(AuthToken token) { - + if (token instanceof NoopToken) { + log.info("Revoke operation is not supported for NoopTokens"); + return; + } + throw new OpenSearchException("Token is not a NoopToken"); } /** @@ -67,6 +77,10 @@ public void revokeToken(AuthToken token) { */ @Override public void resetToken(AuthToken token) { - + if (token instanceof NoopToken) { + log.info("Reset operation is not supported for NoopTokens"); + return; + } + throw new OpenSearchException("Token is not a NoopToken"); } } From 1d05fc25ab400ffbda5ee298df5719a2bf3bc1b8 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 16 May 2023 12:04:08 -0400 Subject: [PATCH 05/21] add getter Signed-off-by: Stephen Crawford --- plugins/identity-shiro/build.gradle | 1 + .../identity/shiro/ShiroIdentityPlugin.java | 11 +++++ .../shiro/ShiroIdentityPluginTests.java | 40 +++++++++++++++++++ .../opensearch/identity/IdentityService.java | 8 ++++ .../identity/noop/NoopIdentityPlugin.java | 9 +++++ .../opensearch/plugins/IdentityPlugin.java | 8 ++++ .../bootstrap/IdentityPluginTests.java | 10 +++-- 7 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java diff --git a/plugins/identity-shiro/build.gradle b/plugins/identity-shiro/build.gradle index c02f3727f935e..4e5f8bd9d7f7f 100644 --- a/plugins/identity-shiro/build.gradle +++ b/plugins/identity-shiro/build.gradle @@ -31,6 +31,7 @@ dependencies { testImplementation project(path: ':plugins:transport-nio') // for http testImplementation "org.mockito:mockito-core:${versions.mockito}" testImplementation project(path: ':client:rest-high-level') + testImplementation 'junit:junit:4.13.2' } /* diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java index 3cfc2fffe9f48..639f5c943ba63 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java @@ -11,6 +11,7 @@ import org.opensearch.identity.Subject; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.identity.tokens.TokenManager; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.common.settings.Settings; import org.opensearch.plugins.Plugin; @@ -50,4 +51,14 @@ public ShiroIdentityPlugin(final Settings settings) { public Subject getSubject() { return new ShiroSubject(authTokenHandler, SecurityUtils.getSubject()); } + + /** + * Return the Shiro Token Handler + * + * @return the Shiro Token Handler + */ + @Override + public TokenManager getTokenManager() { + return this.authTokenHandler; + } } diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java new file mode 100644 index 0000000000000..5a6e1ce8a167a --- /dev/null +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package org.opensearch.identity.shiro; + +import java.util.List; +import org.opensearch.OpenSearchException; +import org.opensearch.common.settings.Settings; +import org.opensearch.identity.IdentityService; +import org.opensearch.plugins.IdentityPlugin; +import org.opensearch.test.OpenSearchTestCase; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThrows; + +public class ShiroIdentityPluginTests extends OpenSearchTestCase { + + public void testSingleIdentityPluginSucceeds() { + IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY); + List pluginList1 = List.of(identityPlugin1); + IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1); + assertThat(identityService1.getTokenManager(), is(instanceOf(ShiroTokenHandler.class))); + } + + public void testMultipleIdentityPluginsFail() { + IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY); + IdentityPlugin identityPlugin2 = new ShiroIdentityPlugin(Settings.EMPTY); + IdentityPlugin identityPlugin3 = new ShiroIdentityPlugin(Settings.EMPTY); + List pluginList = List.of(identityPlugin1, identityPlugin2, identityPlugin3); + Exception ex = assertThrows(OpenSearchException.class, () -> new IdentityService(Settings.EMPTY, pluginList)); + assert (ex.getMessage().contains("Multiple identity plugins are not supported,")); + } + +} diff --git a/server/src/main/java/org/opensearch/identity/IdentityService.java b/server/src/main/java/org/opensearch/identity/IdentityService.java index e7e785ba75231..ab1456cd860ac 100644 --- a/server/src/main/java/org/opensearch/identity/IdentityService.java +++ b/server/src/main/java/org/opensearch/identity/IdentityService.java @@ -11,6 +11,7 @@ import org.opensearch.identity.noop.NoopIdentityPlugin; import java.util.List; import org.opensearch.common.settings.Settings; +import org.opensearch.identity.tokens.TokenManager; import org.opensearch.plugins.IdentityPlugin; import java.util.stream.Collectors; @@ -48,4 +49,11 @@ public IdentityService(final Settings settings, final List ident public Subject getSubject() { return identityPlugin.getSubject(); } + + /** + * Gets the token manager + */ + public TokenManager getTokenManager() { + return identityPlugin.getTokenManager(); + } } diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java b/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java index 437c059684565..164b24f9f5f11 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java @@ -8,6 +8,7 @@ package org.opensearch.identity.noop; +import org.opensearch.identity.tokens.TokenManager; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.identity.Subject; @@ -29,4 +30,12 @@ public Subject getSubject() { return new NoopSubject(); } + /** + * Get a new NoopTokenHandler + * @return Must never return null + */ + @Override + public TokenManager getTokenManager() { + return new NoopTokenHandler(); + } } diff --git a/server/src/main/java/org/opensearch/plugins/IdentityPlugin.java b/server/src/main/java/org/opensearch/plugins/IdentityPlugin.java index 4cb15f4ab3cbe..00f3f8aff585c 100644 --- a/server/src/main/java/org/opensearch/plugins/IdentityPlugin.java +++ b/server/src/main/java/org/opensearch/plugins/IdentityPlugin.java @@ -9,6 +9,7 @@ package org.opensearch.plugins; import org.opensearch.identity.Subject; +import org.opensearch.identity.tokens.TokenManager; /** * Plugin that provides identity and access control for OpenSearch @@ -23,4 +24,11 @@ public interface IdentityPlugin { * Should never return null * */ public Subject getSubject(); + + /** + * Get the Identity Plugin's token manager implementation + * + * Should never return null + */ + public TokenManager getTokenManager(); } diff --git a/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java b/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java index 5595fdd564d01..9e97bd8c921f7 100644 --- a/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java +++ b/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java @@ -13,16 +13,20 @@ import org.opensearch.common.settings.Settings; import org.opensearch.identity.IdentityService; import org.opensearch.identity.noop.NoopIdentityPlugin; +import org.opensearch.identity.noop.NoopTokenHandler; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.test.OpenSearchTestCase; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; public class IdentityPluginTests extends OpenSearchTestCase { public void testSingleIdentityPluginSucceeds() { IdentityPlugin identityPlugin1 = new NoopIdentityPlugin(); - List pluginList = List.of(identityPlugin1); - IdentityService identityService = new IdentityService(Settings.EMPTY, pluginList); - assertTrue(identityService.getSubject().getPrincipal().getName().equalsIgnoreCase("Unauthenticated")); + List pluginList1 = List.of(identityPlugin1); + IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1); + assertTrue(identityService1.getSubject().getPrincipal().getName().equalsIgnoreCase("Unauthenticated")); + assertThat(identityService1.getTokenManager(), is(instanceOf(NoopTokenHandler.class))); } public void testMultipleIdentityPluginsFail() { From 54ef04847678cb885d86eaa98130c7b5fb448ede Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 16 May 2023 12:10:43 -0400 Subject: [PATCH 06/21] Swap to issue word Signed-off-by: Stephen Crawford --- .../java/org/opensearch/identity/shiro/ShiroTokenHandler.java | 2 +- .../java/org/opensearch/identity/noop/NoopTokenHandler.java | 4 ++-- .../java/org/opensearch/identity/tokens/TokenManager.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java index e695e479c57cd..6462c71c897b2 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java @@ -42,7 +42,7 @@ public Optional translateAuthToken(org.opensearch.identity. } @Override - public AuthToken generateToken() { + public AuthToken issueToken() { Subject subject = new ShiroSubject(this, SecurityUtils.getSubject()); final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + generatePassword()).getBytes(UTF_8)); diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java index 6dd1618d3e6c9..7775510a7d080 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java @@ -24,11 +24,11 @@ public class NoopTokenHandler implements TokenManager { private static final Logger log = LogManager.getLogger(IdentityService.class); /** - * Generate a new Noop Token + * Issue a new Noop Token * @return a new Noop Token */ @Override - public AuthToken generateToken() { + public AuthToken issueToken() { return new NoopToken(); } diff --git a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java index 37b9f9156e2f7..edac722149fd1 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java +++ b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java @@ -17,7 +17,7 @@ public interface TokenManager { * Create a new auth token * @return A new auth token */ - public AuthToken generateToken(); + public AuthToken issueToken(); /** * Validate an auth token based on the rules associated with its format From fb7f8aaa11d1508eef4615383394667169a98949 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Thu, 18 May 2023 16:56:44 -0400 Subject: [PATCH 07/21] Increase coverage Signed-off-by: Stephen Crawford --- .../identity/shiro/AuthTokenHandlerTests.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index dc4e21b37b2ae..32930c900656d 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -70,7 +70,14 @@ public void testShouldRevokeTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); shiroAuthTokenHandler.revokeToken(authToken); - assert (authToken.toString().equals("Basic auth token with user=, password=")); + assert(authToken.toString().equals("Basic auth token with user=, password=")); + } + + public void testShouldResetTokenSuccessfully() { + final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); + shiroAuthTokenHandler.resetToken(authToken); + assert(authToken.toString().equals("Basic auth token with user=, password=")); } public void testShouldFailWhenRevokeToken() { @@ -81,9 +88,10 @@ public void testShouldFailWhenRevokeToken() { public void testShouldGetTokenInfoSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assert (authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); + assert(authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); final NoopToken noopAuthToken = new NoopToken(); - assert (noopTokenHandler.getTokenInfo(noopAuthToken).equals("Token is NoopToken")); + assert(noopTokenHandler.getTokenInfo(noopAuthToken).equals("Token is NoopToken")); + assert (noopTokenHandler.getTokenInfo(authToken).equals("Token is not a NoopToken")); } public void testShouldFailGetTokenInfo() { @@ -95,13 +103,17 @@ public void testShouldFailGetTokenInfo() { public void testShouldFailValidateToken() { final AuthToken authToken = new NoopToken(); assertFalse(shiroAuthTokenHandler.validateToken(authToken)); + final AuthToken authToken1 = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + assertFalse(noopTokenHandler.validateToken(authToken1)); } - public void testShouldResetToken(AuthToken token) { - BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + public void testShouldPassThrougbResetToken(AuthToken token) { + NoopToken authToken = new NoopToken(); shiroAuthTokenHandler.resetToken(authToken); - assert (authToken.getPassword().equals("")); - assert (authToken.getUser().equals("")); + } + + public void testShouldGenerateDefaultPassword() { + assertTrue(shiroAuthTokenHandler.generatePassword().equals("superSecurePassword1!")); } public void testShouldPassThrough() { From b86b24e22b1dc3ff72cbd22c2cff67dacf5c0299 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Fri, 19 May 2023 09:46:11 -0400 Subject: [PATCH 08/21] Spotless Signed-off-by: Stephen Crawford --- .../opensearch/identity/shiro/AuthTokenHandlerTests.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 32930c900656d..a81f5faa6725a 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -70,14 +70,14 @@ public void testShouldRevokeTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); shiroAuthTokenHandler.revokeToken(authToken); - assert(authToken.toString().equals("Basic auth token with user=, password=")); + assert (authToken.toString().equals("Basic auth token with user=, password=")); } public void testShouldResetTokenSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); assertTrue(authToken.toString().equals("Basic auth token with user=test, password=te:st")); shiroAuthTokenHandler.resetToken(authToken); - assert(authToken.toString().equals("Basic auth token with user=, password=")); + assert (authToken.toString().equals("Basic auth token with user=, password=")); } public void testShouldFailWhenRevokeToken() { @@ -88,9 +88,9 @@ public void testShouldFailWhenRevokeToken() { public void testShouldGetTokenInfoSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assert(authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); + assert (authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); final NoopToken noopAuthToken = new NoopToken(); - assert(noopTokenHandler.getTokenInfo(noopAuthToken).equals("Token is NoopToken")); + assert (noopTokenHandler.getTokenInfo(noopAuthToken).equals("Token is NoopToken")); assert (noopTokenHandler.getTokenInfo(authToken).equals("Token is not a NoopToken")); } From d90e8e16c33c0a51e5787a3895dd53ee4c780e32 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Fri, 19 May 2023 13:03:04 -0400 Subject: [PATCH 09/21] Create bearer auth token Signed-off-by: Stephen Crawford --- .../identity/tokens/BearerAuthToken.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java diff --git a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java new file mode 100644 index 0000000000000..4c8916e702a4d --- /dev/null +++ b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +package org.opensearch.identity.tokens; + +public class BearerAuthToken implements AuthToken { + + public final String DELIMITER = "."; + public final static String TOKEN_IDENTIFIER = "Bearer"; + + private String header; + private String payload; + private String signature; + + public BearerAuthToken(final String token) { + + String[] tokenComponents = token.split(DELIMITER); + if (tokenComponents.length != 3) { + throw new IllegalStateException("Illegally formed bearer authorization token " + token); + } + header = tokenComponents[0]; + payload = tokenComponents[1]; + signature = tokenComponents[2]; + } + + public String getHeader() { + return header; + } + + public String getPayload() { + return payload; + } + + public String getSignature() { + return signature; + } + + @Override + public String toString() { + return "Bearer auth token with header=" + header + ", payload=" + payload + ", signature=" + signature; + } +} From f8647d62ee578d3faf37ecfb3850959cdff19023 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Fri, 19 May 2023 13:14:06 -0400 Subject: [PATCH 10/21] java doc Signed-off-by: Stephen Crawford --- .../java/org/opensearch/identity/tokens/BearerAuthToken.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java index 4c8916e702a4d..5a1dfa8e61660 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java @@ -8,6 +8,9 @@ package org.opensearch.identity.tokens; +/** + * Bearer (JWT) Authentication Token in a http request header + */ public class BearerAuthToken implements AuthToken { public final String DELIMITER = "."; From 3fd358ff4ac0a1f0637845aaa4120d5d88c3cb2b Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Fri, 19 May 2023 14:04:51 -0400 Subject: [PATCH 11/21] Add full token string Signed-off-by: Stephen Crawford --- .../opensearch/identity/tokens/BearerAuthToken.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java index 5a1dfa8e61660..bd7f35753d08c 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java @@ -20,12 +20,15 @@ public class BearerAuthToken implements AuthToken { private String payload; private String signature; + private String completeToken; + public BearerAuthToken(final String token) { String[] tokenComponents = token.split(DELIMITER); if (tokenComponents.length != 3) { throw new IllegalStateException("Illegally formed bearer authorization token " + token); } + completeToken = token; header = tokenComponents[0]; payload = tokenComponents[1]; signature = tokenComponents[2]; @@ -43,6 +46,14 @@ public String getSignature() { return signature; } + public String getCompleteToken() { + return completeToken; + } + + public String getTokenIdentifier() { + return TOKEN_IDENTIFIER; + } + @Override public String toString() { return "Bearer auth token with header=" + header + ", payload=" + payload + ", signature=" + signature; From b05964fdd4709d45dd5482c38725ad085028a903 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Fri, 19 May 2023 14:51:13 -0400 Subject: [PATCH 12/21] Add coverage for bearer token type Signed-off-by: Stephen Crawford --- .../identity/shiro/AuthTokenHandlerTests.java | 13 +++++++++++++ .../opensearch/identity/tokens/BearerAuthToken.java | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index a81f5faa6725a..212a74a34ec23 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -14,6 +14,7 @@ import org.opensearch.identity.noop.NoopTokenHandler; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; +import org.opensearch.identity.tokens.BearerAuthToken; import org.opensearch.identity.tokens.NoopToken; import org.opensearch.test.OpenSearchTestCase; import org.junit.Before; @@ -128,4 +129,16 @@ public void testShouldFailPassThrough() { assertThrows(OpenSearchException.class, () -> noopTokenHandler.revokeToken(authToken)); } + public void testVerifyBearerTokenObject() { + BearerAuthToken testGoodToken = new BearerAuthToken("header.payload.signature"); + IllegalStateException exception = assertThrows(IllegalStateException.class, () -> new BearerAuthToken("asddfhadfasdfad")); + assert(exception.getMessage().contains("Illegally formed bearer authorization token ")); + assertEquals(testGoodToken.getCompleteToken(), "header.payload.signature"); + assertEquals(testGoodToken.getTokenIdentifier(), "Bearer"); + assertEquals(testGoodToken.getHeader(), "header"); + assertEquals(testGoodToken.getPayload(), "payload"); + assertEquals(testGoodToken.getSignature(), "signature"); + assertEquals(testGoodToken.toString(), "Bearer auth token with header=header, payload=payload, signature=signature"); + } + } diff --git a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java index bd7f35753d08c..63a04ffd6cdcb 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java @@ -13,7 +13,7 @@ */ public class BearerAuthToken implements AuthToken { - public final String DELIMITER = "."; + public final String DELIMITER = "\\."; public final static String TOKEN_IDENTIFIER = "Bearer"; private String header; From 17e5a44fc3f5f59b0dbf481a6c14584f4187b29e Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Mon, 22 May 2023 10:17:00 -0400 Subject: [PATCH 13/21] spotless Signed-off-by: Stephen Crawford --- .../org/opensearch/identity/shiro/AuthTokenHandlerTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 212a74a34ec23..5d01165a2a15b 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -132,7 +132,7 @@ public void testShouldFailPassThrough() { public void testVerifyBearerTokenObject() { BearerAuthToken testGoodToken = new BearerAuthToken("header.payload.signature"); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> new BearerAuthToken("asddfhadfasdfad")); - assert(exception.getMessage().contains("Illegally formed bearer authorization token ")); + assert (exception.getMessage().contains("Illegally formed bearer authorization token ")); assertEquals(testGoodToken.getCompleteToken(), "header.payload.signature"); assertEquals(testGoodToken.getTokenIdentifier(), "Bearer"); assertEquals(testGoodToken.getHeader(), "header"); From 1e9afe315b390b21fb5e31bbaea40a39b6da401d Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 30 May 2023 12:19:20 -0400 Subject: [PATCH 14/21] Implement Password generation for development Signed-off-by: Stephen Crawford --- plugins/identity-shiro/build.gradle | 3 + .../licenses/passay-1.6.3.jar.sha1 | 1 + .../licenses/passay-LICENSE.txt | 202 ++++++++++++++++++ .../identity-shiro/licenses/passay-NOTICE.txt | 0 .../identity/shiro/ShiroIdentityPlugin.java | 4 +- .../identity/shiro/ShiroSubject.java | 4 +- ...kenHandler.java => ShiroTokenManager.java} | 60 +++++- .../identity/shiro/AuthTokenHandlerTests.java | 97 +++++---- .../shiro/ShiroIdentityPluginTests.java | 2 +- .../identity/shiro/ShiroSubjectTests.java | 4 +- .../identity/noop/NoopIdentityPlugin.java | 4 +- ...okenHandler.java => NoopTokenManager.java} | 38 ++-- .../opensearch/identity/tokens/NoopToken.java | 25 --- .../bootstrap/IdentityPluginTests.java | 4 +- 14 files changed, 339 insertions(+), 109 deletions(-) create mode 100644 plugins/identity-shiro/licenses/passay-1.6.3.jar.sha1 create mode 100644 plugins/identity-shiro/licenses/passay-LICENSE.txt create mode 100644 plugins/identity-shiro/licenses/passay-NOTICE.txt rename plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/{ShiroTokenHandler.java => ShiroTokenManager.java} (55%) rename server/src/main/java/org/opensearch/identity/noop/{NoopTokenHandler.java => NoopTokenManager.java} (55%) delete mode 100644 server/src/main/java/org/opensearch/identity/tokens/NoopToken.java diff --git a/plugins/identity-shiro/build.gradle b/plugins/identity-shiro/build.gradle index 4e5f8bd9d7f7f..5c70463862ba4 100644 --- a/plugins/identity-shiro/build.gradle +++ b/plugins/identity-shiro/build.gradle @@ -18,6 +18,7 @@ opensearchplugin { dependencies { implementation 'org.apache.shiro:shiro-core:1.11.0' + // Needed for shiro implementation "org.slf4j:slf4j-api:${versions.slf4j}" @@ -25,6 +26,8 @@ dependencies { implementation 'commons-logging:commons-logging:1.2' implementation 'commons-lang:commons-lang:2.6' + implementation 'org.passay:passay:1.6.3' + implementation "org.bouncycastle:bcprov-jdk15on:${versions.bouncycastle}" testImplementation project(path: ':modules:transport-netty4') // for http diff --git a/plugins/identity-shiro/licenses/passay-1.6.3.jar.sha1 b/plugins/identity-shiro/licenses/passay-1.6.3.jar.sha1 new file mode 100644 index 0000000000000..52ac0b1a81469 --- /dev/null +++ b/plugins/identity-shiro/licenses/passay-1.6.3.jar.sha1 @@ -0,0 +1 @@ +bae4754c87297f5600e4071b2596c0b6625cf92b \ No newline at end of file diff --git a/plugins/identity-shiro/licenses/passay-LICENSE.txt b/plugins/identity-shiro/licenses/passay-LICENSE.txt new file mode 100644 index 0000000000000..d645695673349 --- /dev/null +++ b/plugins/identity-shiro/licenses/passay-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/identity-shiro/licenses/passay-NOTICE.txt b/plugins/identity-shiro/licenses/passay-NOTICE.txt new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java index 639f5c943ba63..c3c08b1359aaa 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroIdentityPlugin.java @@ -27,7 +27,7 @@ public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin private Logger log = LogManager.getLogger(this.getClass()); private final Settings settings; - private final ShiroTokenHandler authTokenHandler; + private final ShiroTokenManager authTokenHandler; /** * Create a new instance of the Shiro Identity Plugin @@ -36,7 +36,7 @@ public final class ShiroIdentityPlugin extends Plugin implements IdentityPlugin */ public ShiroIdentityPlugin(final Settings settings) { this.settings = settings; - authTokenHandler = new ShiroTokenHandler(); + authTokenHandler = new ShiroTokenManager(); SecurityManager securityManager = new ShiroSecurityManager(); SecurityUtils.setSecurityManager(securityManager); diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java index 414e2aa921d29..89a801d1aab76 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroSubject.java @@ -20,7 +20,7 @@ * @opensearch.experimental */ public class ShiroSubject implements Subject { - private final ShiroTokenHandler authTokenHandler; + private final ShiroTokenManager authTokenHandler; private final org.apache.shiro.subject.Subject shiroSubject; /** @@ -29,7 +29,7 @@ public class ShiroSubject implements Subject { * @param authTokenHandler Used to extract auth header info * @param subject The specific subject being authc/z'd */ - public ShiroSubject(final ShiroTokenHandler authTokenHandler, final org.apache.shiro.subject.Subject subject) { + public ShiroSubject(final ShiroTokenManager authTokenHandler, final org.apache.shiro.subject.Subject subject) { this.authTokenHandler = Objects.requireNonNull(authTokenHandler); this.shiroSubject = Objects.requireNonNull(subject); } diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java similarity index 55% rename from plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java rename to plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java index 6462c71c897b2..44696f05b714a 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenHandler.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java @@ -8,16 +8,27 @@ package org.opensearch.identity.shiro; +import java.util.Arrays; import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Optional; - +import java.util.Random; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; +import org.opensearch.common.Randomness; +import org.opensearch.identity.IdentityService; import org.opensearch.identity.Subject; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.identity.tokens.TokenManager; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.PasswordGenerator; import static java.nio.charset.StandardCharsets.UTF_8; /** @@ -25,7 +36,11 @@ * * @opensearch.experimental */ -class ShiroTokenHandler implements TokenManager { +class ShiroTokenManager implements TokenManager { + + private static final Logger log = LogManager.getLogger(IdentityService.class); + + private static Map shiroTokenPasswordMap = new HashMap<>(); /** * Translates into shiro auth token from the given header token @@ -45,20 +60,22 @@ public Optional translateAuthToken(org.opensearch.identity. public AuthToken issueToken() { Subject subject = new ShiroSubject(this, SecurityUtils.getSubject()); - final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + generatePassword()).getBytes(UTF_8)); + String password = generatePassword(); + final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + password).getBytes(UTF_8)); final String usernamePassword = new String(rawEncoded, UTF_8); final String header = "Basic " + usernamePassword; + BasicAuthToken token = new BasicAuthToken(header); + shiroTokenPasswordMap.put(token, password); - return new BasicAuthToken(header); + return token; } @Override public boolean validateToken(AuthToken token) { if (token instanceof BasicAuthToken) { final BasicAuthToken basicAuthToken = (BasicAuthToken) token; - if (basicAuthToken.getUser().equals(SecurityUtils.getSubject()) && basicAuthToken.getPassword().equals(generatePassword())) { - return true; - } + return basicAuthToken.getUser().equals(SecurityUtils.getSubject().toString()) + && basicAuthToken.getPassword().equals(shiroTokenPasswordMap.get(basicAuthToken)); } return false; } @@ -90,8 +107,35 @@ public void resetToken(AuthToken token) { } } + /** + * When the ShiroTokenManager is in use, a random password is generated for each token and is then output to the logs. + * The password is used for development only. + * @return A randomly generated password for development + */ public String generatePassword() { - return "superSecurePassword1!"; + + CharacterRule lowercaseCharacterRule = new CharacterRule(EnglishCharacterData.LowerCase, 1); + CharacterRule uppercaseCharacterRule = new CharacterRule(EnglishCharacterData.UpperCase, 1); + CharacterRule numericCharacterRule = new CharacterRule(EnglishCharacterData.Digit, 1); + CharacterRule specialCharacterRule = new CharacterRule(EnglishCharacterData.Special, 1); + + List rules = Arrays.asList( + lowercaseCharacterRule, + uppercaseCharacterRule, + numericCharacterRule, + specialCharacterRule + ); + PasswordGenerator passwordGenerator = new PasswordGenerator(); + + Random random = Randomness.get(); + + String password = passwordGenerator.generatePassword(random.nextInt(8) + 8, rules); // Generate a 8 to 16 char password + log.info("Generated password: " + password); + return password; + } + + public Map getShiroTokenPasswordMap() { + return shiroTokenPasswordMap; } } diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 5d01165a2a15b..9640bd504a0dc 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -8,33 +8,35 @@ package org.opensearch.identity.shiro; +import java.util.Optional; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; -import org.opensearch.OpenSearchException; -import org.opensearch.identity.noop.NoopTokenHandler; +import org.junit.Before; +import org.opensearch.identity.noop.NoopTokenManager; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.identity.tokens.BearerAuthToken; -import org.opensearch.identity.tokens.NoopToken; import org.opensearch.test.OpenSearchTestCase; -import org.junit.Before; - -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.instanceOf; +import org.passay.CharacterCharacteristicsRule; +import org.passay.CharacterRule; +import org.passay.EnglishCharacterData; +import org.passay.LengthRule; +import org.passay.PasswordData; +import org.passay.PasswordValidator; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; -import java.util.Optional; - public class AuthTokenHandlerTests extends OpenSearchTestCase { - private ShiroTokenHandler shiroAuthTokenHandler; - private NoopTokenHandler noopTokenHandler; + private ShiroTokenManager shiroAuthTokenHandler; + private NoopTokenManager noopTokenManager; @Before public void testSetup() { - shiroAuthTokenHandler = new ShiroTokenHandler(); - noopTokenHandler = new NoopTokenHandler(); + shiroAuthTokenHandler = new ShiroTokenManager(); + noopTokenManager = new NoopTokenManager(); } public void testShouldExtractBasicAuthTokenSuccessfully() { @@ -82,51 +84,45 @@ public void testShouldResetTokenSuccessfully() { } public void testShouldFailWhenRevokeToken() { - final NoopToken authToken = new NoopToken(); - assert (authToken.getTokenIdentifier().equals("Noop")); - assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.revokeToken(authToken)); + final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); + assert (bearerAuthToken.getTokenIdentifier().equals("Bearer")); + assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.revokeToken(bearerAuthToken)); } public void testShouldGetTokenInfoSuccessfully() { final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); assert (authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); - final NoopToken noopAuthToken = new NoopToken(); - assert (noopTokenHandler.getTokenInfo(noopAuthToken).equals("Token is NoopToken")); - assert (noopTokenHandler.getTokenInfo(authToken).equals("Token is not a NoopToken")); + final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); + assert (noopTokenManager.getTokenInfo(authToken).equals("Token is of type: " + authToken.getClass())); + assert (noopTokenManager.getTokenInfo(bearerAuthToken).equals("Token is of type: " + bearerAuthToken.getClass())); } public void testShouldFailGetTokenInfo() { - final NoopToken authToken = new NoopToken(); - assert (authToken.getTokenIdentifier().equals("Noop")); - assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.getTokenInfo(authToken)); + final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); + assert (bearerAuthToken.getTokenIdentifier().equals("Bearer")); + assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.getTokenInfo(bearerAuthToken)); } public void testShouldFailValidateToken() { - final AuthToken authToken = new NoopToken(); - assertFalse(shiroAuthTokenHandler.validateToken(authToken)); - final AuthToken authToken1 = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assertFalse(noopTokenHandler.validateToken(authToken1)); + final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); + assertFalse(shiroAuthTokenHandler.validateToken(bearerAuthToken)); } - public void testShouldPassThrougbResetToken(AuthToken token) { - NoopToken authToken = new NoopToken(); - shiroAuthTokenHandler.resetToken(authToken); + public void testShoudPassMapLookupWithToken() { + final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); + shiroAuthTokenHandler.getShiroTokenPasswordMap().put(authToken, "te:st"); + assertTrue(authToken.getPassword().equals(shiroAuthTokenHandler.getShiroTokenPasswordMap().get(authToken))); } - public void testShouldGenerateDefaultPassword() { - assertTrue(shiroAuthTokenHandler.generatePassword().equals("superSecurePassword1!")); + public void testShouldPassThrougbResetToken(AuthToken token) { + final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); + shiroAuthTokenHandler.resetToken(bearerAuthToken); } public void testShouldPassThrough() { - final NoopToken authToken = new NoopToken(); - noopTokenHandler.resetToken(authToken); - noopTokenHandler.revokeToken(authToken); - } - - public void testShouldFailPassThrough() { - BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assertThrows(OpenSearchException.class, () -> noopTokenHandler.resetToken(authToken)); - assertThrows(OpenSearchException.class, () -> noopTokenHandler.revokeToken(authToken)); + final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); + noopTokenManager.resetToken(bearerAuthToken); + noopTokenManager.revokeToken(bearerAuthToken); } public void testVerifyBearerTokenObject() { @@ -141,4 +137,25 @@ public void testVerifyBearerTokenObject() { assertEquals(testGoodToken.toString(), "Bearer auth token with header=header, payload=payload, signature=signature"); } + public void testGeneratedPasswordContents() { + String password = shiroAuthTokenHandler.generatePassword(); + PasswordData data = new PasswordData(password); + + LengthRule lengthRule = new LengthRule(8, 16); + + CharacterCharacteristicsRule characteristicsRule = new CharacterCharacteristicsRule(); + + // Define M (3 in this case) + characteristicsRule.setNumberOfCharacteristics(3); + + // Define elements of N (upper, lower, digit, symbol) + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.UpperCase, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.LowerCase, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Digit, 1)); + characteristicsRule.getRules().add(new CharacterRule(EnglishCharacterData.Special, 1)); + + PasswordValidator validator = new PasswordValidator(lengthRule, characteristicsRule); + validator.validate(data); + } + } diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java index 5a6e1ce8a167a..f06dff7eea382 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroIdentityPluginTests.java @@ -25,7 +25,7 @@ public void testSingleIdentityPluginSucceeds() { IdentityPlugin identityPlugin1 = new ShiroIdentityPlugin(Settings.EMPTY); List pluginList1 = List.of(identityPlugin1); IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1); - assertThat(identityService1.getTokenManager(), is(instanceOf(ShiroTokenHandler.class))); + assertThat(identityService1.getTokenManager(), is(instanceOf(ShiroTokenManager.class))); } public void testMultipleIdentityPluginsFail() { diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java index 311df738108c6..930945e9a2d8d 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/ShiroSubjectTests.java @@ -25,13 +25,13 @@ public class ShiroSubjectTests extends OpenSearchTestCase { private org.apache.shiro.subject.Subject shiroSubject; - private ShiroTokenHandler authTokenHandler; + private ShiroTokenManager authTokenHandler; private ShiroSubject subject; @Before public void setup() { shiroSubject = mock(org.apache.shiro.subject.Subject.class); - authTokenHandler = mock(ShiroTokenHandler.class); + authTokenHandler = mock(ShiroTokenManager.class); subject = new ShiroSubject(authTokenHandler, shiroSubject); } diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java b/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java index 164b24f9f5f11..c6ed8d57da435 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopIdentityPlugin.java @@ -31,11 +31,11 @@ public Subject getSubject() { } /** - * Get a new NoopTokenHandler + * Get a new NoopTokenManager * @return Must never return null */ @Override public TokenManager getTokenManager() { - return new NoopTokenHandler(); + return new NoopTokenManager(); } } diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java similarity index 55% rename from server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java rename to server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java index 7775510a7d080..0abfdf2506570 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenHandler.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java @@ -10,16 +10,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.opensearch.OpenSearchException; import org.opensearch.identity.IdentityService; import org.opensearch.identity.tokens.AuthToken; -import org.opensearch.identity.tokens.NoopToken; import org.opensearch.identity.tokens.TokenManager; /** * This class represents a Noop Token Manager */ -public class NoopTokenHandler implements TokenManager { +public class NoopTokenManager implements TokenManager { private static final Logger log = LogManager.getLogger(IdentityService.class); @@ -29,33 +27,29 @@ public class NoopTokenHandler implements TokenManager { */ @Override public AuthToken issueToken() { - return new NoopToken(); + return new AuthToken() { + }; } /** * Validate a token * @param token The token to be validated - * @return If the token is a Noop Token, then pass with True; otherwise fail with False. + * @return true */ @Override public boolean validateToken(AuthToken token) { - if (token instanceof NoopToken) { - return true; - } - return false; + log.info("Validating a token with NoopTokenManager"); + return true; } /** - * Get token info, there should not be any token info so just return whether the token is a NoopToken + * Get token class * @param token The auth token to be parsed - * @return A String stating the token is a NoopToken or is not a NopToken + * @return A description of the token's type */ @Override public String getTokenInfo(AuthToken token) { - if (token instanceof NoopToken) { - return "Token is NoopToken"; - } - return "Token is not a NoopToken"; + return "Token is of type: " + token.getClass(); } /** @@ -64,11 +58,8 @@ public String getTokenInfo(AuthToken token) { */ @Override public void revokeToken(AuthToken token) { - if (token instanceof NoopToken) { - log.info("Revoke operation is not supported for NoopTokens"); - return; - } - throw new OpenSearchException("Token is not a NoopToken"); + log.info("Revoke operation is not supported for NoopTokens"); + return; } /** @@ -77,10 +68,7 @@ public void revokeToken(AuthToken token) { */ @Override public void resetToken(AuthToken token) { - if (token instanceof NoopToken) { - log.info("Reset operation is not supported for NoopTokens"); - return; - } - throw new OpenSearchException("Token is not a NoopToken"); + log.info("Reset operation is not supported for NoopTokens"); + return; } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java b/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java deleted file mode 100644 index 2e19c740672ce..0000000000000 --- a/server/src/main/java/org/opensearch/identity/tokens/NoopToken.java +++ /dev/null @@ -1,25 +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. - */ - -package org.opensearch.identity.tokens; - -/** - * A NoopToken is a pass-through AuthToken - */ -public class NoopToken implements AuthToken { - public final static String TOKEN_IDENTIFIER = "Noop"; - - /** - * Returns the TokenIdentifier of Noop - * @return The token identifier "Noop" - */ - public String getTokenIdentifier() { - return TOKEN_IDENTIFIER; - } - -} diff --git a/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java b/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java index 9e97bd8c921f7..b84a9a87ec77e 100644 --- a/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java +++ b/server/src/test/java/org/opensearch/bootstrap/IdentityPluginTests.java @@ -13,7 +13,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.identity.IdentityService; import org.opensearch.identity.noop.NoopIdentityPlugin; -import org.opensearch.identity.noop.NoopTokenHandler; +import org.opensearch.identity.noop.NoopTokenManager; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.test.OpenSearchTestCase; import static org.hamcrest.Matchers.instanceOf; @@ -26,7 +26,7 @@ public void testSingleIdentityPluginSucceeds() { List pluginList1 = List.of(identityPlugin1); IdentityService identityService1 = new IdentityService(Settings.EMPTY, pluginList1); assertTrue(identityService1.getSubject().getPrincipal().getName().equalsIgnoreCase("Unauthenticated")); - assertThat(identityService1.getTokenManager(), is(instanceOf(NoopTokenHandler.class))); + assertThat(identityService1.getTokenManager(), is(instanceOf(NoopTokenManager.class))); } public void testMultipleIdentityPluginsFail() { From ff876aba032b0138a7fde796e3fd5c0b1f95186f Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 30 May 2023 13:15:31 -0400 Subject: [PATCH 15/21] fix audit Signed-off-by: Stephen Crawford --- plugins/identity-shiro/build.gradle | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/identity-shiro/build.gradle b/plugins/identity-shiro/build.gradle index 5c70463862ba4..22dc21864b620 100644 --- a/plugins/identity-shiro/build.gradle +++ b/plugins/identity-shiro/build.gradle @@ -33,8 +33,8 @@ dependencies { testImplementation project(path: ':modules:transport-netty4') // for http testImplementation project(path: ':plugins:transport-nio') // for http testImplementation "org.mockito:mockito-core:${versions.mockito}" - testImplementation project(path: ':client:rest-high-level') - testImplementation 'junit:junit:4.13.2' + testImplementation project(path: ':client:rest-high-level') + testImplementation 'junit:junit:4.13.2' } /* @@ -50,6 +50,7 @@ internalClusterTest { } thirdPartyAudit.ignoreMissingClasses( + 'com.google.common.hash.BloomFilter', 'javax.servlet.ServletContextEvent', 'javax.servlet.ServletContextListener', 'org.apache.avalon.framework.logger.Logger', @@ -65,7 +66,10 @@ thirdPartyAudit.ignoreMissingClasses( 'org.apache.log4j.Level', 'org.apache.log4j.Logger', 'org.apache.log4j.Priority', + 'org.cryptacular.bean.HashBean', 'org.slf4j.impl.StaticLoggerBinder', 'org.slf4j.impl.StaticMDCBinder', - 'org.slf4j.impl.StaticMarkerBinder' + 'org.slf4j.impl.StaticMarkerBinder', + 'org.springframework.context.MessageSource', + 'org.springframework.context.support.MessageSourceAccessor' ) From d353f62a25a9e7795ab437416306acb6e4a4b045 Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 30 May 2023 14:25:58 -0400 Subject: [PATCH 16/21] Update plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java Co-authored-by: Andriy Redko Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .../java/org/opensearch/identity/shiro/ShiroTokenManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java index 44696f05b714a..44b829957876d 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java @@ -134,7 +134,7 @@ public String generatePassword() { return password; } - public Map getShiroTokenPasswordMap() { +Map getShiroTokenPasswordMap() { return shiroTokenPasswordMap; } From 2b1a1feee1f645a9c5ba9744ef26ee9c81ea43de Mon Sep 17 00:00:00 2001 From: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> Date: Tue, 30 May 2023 14:26:07 -0400 Subject: [PATCH 17/21] Update server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java Co-authored-by: Andriy Redko Signed-off-by: Stephen Crawford <65832608+scrawfor99@users.noreply.github.com> --- .../java/org/opensearch/identity/tokens/BearerAuthToken.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java index 63a04ffd6cdcb..eac164af1c5d3 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java +++ b/server/src/main/java/org/opensearch/identity/tokens/BearerAuthToken.java @@ -26,7 +26,7 @@ public BearerAuthToken(final String token) { String[] tokenComponents = token.split(DELIMITER); if (tokenComponents.length != 3) { - throw new IllegalStateException("Illegally formed bearer authorization token " + token); + throw new IllegalArgumentException("Illegally formed bearer authorization token " + token); } completeToken = token; header = tokenComponents[0]; From 57acaca174d8478ac453a823bc859b2ff0303fe0 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 30 May 2023 14:37:13 -0400 Subject: [PATCH 18/21] spotless Signed-off-by: Stephen Crawford --- .../java/org/opensearch/identity/shiro/ShiroTokenManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java index 44b829957876d..7406fe3835130 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java @@ -134,7 +134,7 @@ public String generatePassword() { return password; } -Map getShiroTokenPasswordMap() { + Map getShiroTokenPasswordMap() { return shiroTokenPasswordMap; } From 323d6f694e59b2ce5f5db3f2d45b4d7f866b4644 Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 30 May 2023 15:15:08 -0400 Subject: [PATCH 19/21] Swap exception type Signed-off-by: Stephen Crawford --- .../org/opensearch/identity/shiro/AuthTokenHandlerTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 9640bd504a0dc..8696a19f1f43c 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -127,7 +127,7 @@ public void testShouldPassThrough() { public void testVerifyBearerTokenObject() { BearerAuthToken testGoodToken = new BearerAuthToken("header.payload.signature"); - IllegalStateException exception = assertThrows(IllegalStateException.class, () -> new BearerAuthToken("asddfhadfasdfad")); + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new BearerAuthToken("asddfhadfasdfad")); assert (exception.getMessage().contains("Illegally formed bearer authorization token ")); assertEquals(testGoodToken.getCompleteToken(), "header.payload.signature"); assertEquals(testGoodToken.getTokenIdentifier(), "Bearer"); From 4e48c620582f88228d9413ce025af27cc32f450e Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Thu, 1 Jun 2023 09:26:48 -0400 Subject: [PATCH 20/21] Fix changelog Signed-off-by: Stephen Crawford --- CHANGELOG.md | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 25a2c0bc78390..7f03a6d24ff72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -79,30 +79,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Security ## [Unreleased 2.x] -### Added -- [Extensions] Moving Extensions APIs to support cross versions via protobuf. ([#7402](https://github.com/opensearch-project/OpenSearch/issues/7402)) -- [Extensions] Add IdentityPlugin into core to support Extension identities ([#7246](https://github.com/opensearch-project/OpenSearch/pull/7246)) -- Add connectToNodeAsExtension in TransportService ([#6866](https://github.com/opensearch-project/OpenSearch/pull/6866)) -- [Search Pipelines] Accept pipelines defined in search source ([#7253](https://github.com/opensearch-project/OpenSearch/pull/7253)) -- [Search Pipelines] Add `default_search_pipeline` index setting ([#7470](https://github.com/opensearch-project/OpenSearch/pull/7470)) -- [Search Pipelines] Add RenameFieldResponseProcessor for Search Pipelines ([#7377](https://github.com/opensearch-project/OpenSearch/pull/7377)) -- [Search Pipelines] Split search pipeline processor factories by type ([#7597](https://github.com/opensearch-project/OpenSearch/pull/7597)) -- [Search Pipelines] Add script processor ([#7607](https://github.com/opensearch-project/OpenSearch/pull/7607)) -- Add descending order search optimization through reverse segment read. ([#7244](https://github.com/opensearch-project/OpenSearch/pull/7244)) -- Add 'unsigned_long' numeric field type ([#6237](https://github.com/opensearch-project/OpenSearch/pull/6237)) -- Add back primary shard preference for queries ([#7375](https://github.com/opensearch-project/OpenSearch/pull/7375)) -- Add task cancellation timestamp in task API ([#7455](https://github.com/opensearch-project/OpenSearch/pull/7455)) -- Adds ExtensionsManager.lookupExtensionSettingsById ([#7466](https://github.com/opensearch-project/OpenSearch/pull/7466)) -- SegRep with Remote: Add hook for publishing checkpoint notifications after segment upload to remote store ([#7394](https://github.com/opensearch-project/OpenSearch/pull/7394)) -- Add TokenManager Interface ([#7452](https://github.com/opensearch-project/OpenSearch/pull/7452)) -- Add search_after query optimizations with shard/segment short cutting ([#7453](https://github.com/opensearch-project/OpenSearch/pull/7453)) -- Provide mechanism to configure XContent parsing constraints (after update to Jackson 2.15.0 and above) ([#7550](https://github.com/opensearch-project/OpenSearch/pull/7550)) -- Support to clear filecache using clear indices cache API ([#7498](https://github.com/opensearch-project/OpenSearch/pull/7498)) -- Create NamedRoute to map extension routes to a shortened name ([#6870](https://github.com/opensearch-project/OpenSearch/pull/6870)) -- Added @dbwiddis as on OpenSearch maintainer ([#7665](https://github.com/opensearch-project/OpenSearch/pull/7665)) -- [Extensions] Add ExtensionAwarePlugin extension point to add custom settings for extensions ([#7526](https://github.com/opensearch-project/OpenSearch/pull/7526)) -- Add new cluster setting to set default index replication type ([#7420](https://github.com/opensearch-project/OpenSearch/pull/7420)) - +### Add TokenManager Interface ([#7452](https://github.com/opensearch-project/OpenSearch/pull/7452)) ### Dependencies From e156bfe76376e677d40262f27e2117bc4cc2cfbe Mon Sep 17 00:00:00 2001 From: Stephen Crawford Date: Tue, 6 Jun 2023 09:46:14 -0400 Subject: [PATCH 21/21] Update token manager Signed-off-by: Stephen Crawford --- .../identity/shiro/ShiroTokenManager.java | 10 +---- .../identity/shiro/AuthTokenHandlerTests.java | 14 ------ .../identity/noop/NoopTokenManager.java | 43 +------------------ .../identity/tokens/TokenManager.java | 29 +------------ 4 files changed, 5 insertions(+), 91 deletions(-) diff --git a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java index 7406fe3835130..110095a5cd4ef 100644 --- a/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java +++ b/plugins/identity-shiro/src/main/java/org/opensearch/identity/shiro/ShiroTokenManager.java @@ -22,7 +22,6 @@ import org.apache.shiro.authc.UsernamePasswordToken; import org.opensearch.common.Randomness; import org.opensearch.identity.IdentityService; -import org.opensearch.identity.Subject; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.identity.tokens.TokenManager; @@ -57,11 +56,10 @@ public Optional translateAuthToken(org.opensearch.identity. } @Override - public AuthToken issueToken() { + public AuthToken issueToken(String audience) { - Subject subject = new ShiroSubject(this, SecurityUtils.getSubject()); String password = generatePassword(); - final byte[] rawEncoded = Base64.getEncoder().encode((subject.getPrincipal().getName() + ":" + password).getBytes(UTF_8)); + final byte[] rawEncoded = Base64.getEncoder().encode((audience + ":" + password).getBytes(UTF_8)); final String usernamePassword = new String(rawEncoded, UTF_8); final String header = "Basic " + usernamePassword; BasicAuthToken token = new BasicAuthToken(header); @@ -70,7 +68,6 @@ public AuthToken issueToken() { return token; } - @Override public boolean validateToken(AuthToken token) { if (token instanceof BasicAuthToken) { final BasicAuthToken basicAuthToken = (BasicAuthToken) token; @@ -80,7 +77,6 @@ public boolean validateToken(AuthToken token) { return false; } - @Override public String getTokenInfo(AuthToken token) { if (token instanceof BasicAuthToken) { final BasicAuthToken basicAuthToken = (BasicAuthToken) token; @@ -89,7 +85,6 @@ public String getTokenInfo(AuthToken token) { throw new UnsupportedAuthenticationToken(); } - @Override public void revokeToken(AuthToken token) { if (token instanceof BasicAuthToken) { final BasicAuthToken basicAuthToken = (BasicAuthToken) token; @@ -99,7 +94,6 @@ public void revokeToken(AuthToken token) { throw new UnsupportedAuthenticationToken(); } - @Override public void resetToken(AuthToken token) { if (token instanceof BasicAuthToken) { final BasicAuthToken basicAuthToken = (BasicAuthToken) token; diff --git a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java index 8696a19f1f43c..540fed368aeda 100644 --- a/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java +++ b/plugins/identity-shiro/src/test/java/org/opensearch/identity/shiro/AuthTokenHandlerTests.java @@ -89,14 +89,6 @@ public void testShouldFailWhenRevokeToken() { assertThrows(UnsupportedAuthenticationToken.class, () -> shiroAuthTokenHandler.revokeToken(bearerAuthToken)); } - public void testShouldGetTokenInfoSuccessfully() { - final BasicAuthToken authToken = new BasicAuthToken("Basic dGVzdDp0ZTpzdA=="); - assert (authToken.toString().equals(shiroAuthTokenHandler.getTokenInfo(authToken))); - final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); - assert (noopTokenManager.getTokenInfo(authToken).equals("Token is of type: " + authToken.getClass())); - assert (noopTokenManager.getTokenInfo(bearerAuthToken).equals("Token is of type: " + bearerAuthToken.getClass())); - } - public void testShouldFailGetTokenInfo() { final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); assert (bearerAuthToken.getTokenIdentifier().equals("Bearer")); @@ -119,12 +111,6 @@ public void testShouldPassThrougbResetToken(AuthToken token) { shiroAuthTokenHandler.resetToken(bearerAuthToken); } - public void testShouldPassThrough() { - final BearerAuthToken bearerAuthToken = new BearerAuthToken("header.payload.signature"); - noopTokenManager.resetToken(bearerAuthToken); - noopTokenManager.revokeToken(bearerAuthToken); - } - public void testVerifyBearerTokenObject() { BearerAuthToken testGoodToken = new BearerAuthToken("header.payload.signature"); IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> new BearerAuthToken("asddfhadfasdfad")); diff --git a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java index 0abfdf2506570..a55f28e02a8aa 100644 --- a/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java +++ b/server/src/main/java/org/opensearch/identity/noop/NoopTokenManager.java @@ -26,49 +26,8 @@ public class NoopTokenManager implements TokenManager { * @return a new Noop Token */ @Override - public AuthToken issueToken() { + public AuthToken issueToken(String audience) { return new AuthToken() { }; } - - /** - * Validate a token - * @param token The token to be validated - * @return true - */ - @Override - public boolean validateToken(AuthToken token) { - log.info("Validating a token with NoopTokenManager"); - return true; - } - - /** - * Get token class - * @param token The auth token to be parsed - * @return A description of the token's type - */ - @Override - public String getTokenInfo(AuthToken token) { - return "Token is of type: " + token.getClass(); - } - - /** - * Revoking a Noop Token should not do anything - * @param token The Auth Token to be revoked - */ - @Override - public void revokeToken(AuthToken token) { - log.info("Revoke operation is not supported for NoopTokens"); - return; - } - - /** - * Refreshing a NoopToken also not do anything - * @param token The token to be refreshed - */ - @Override - public void resetToken(AuthToken token) { - log.info("Reset operation is not supported for NoopTokens"); - return; - } } diff --git a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java index edac722149fd1..029ce430e7532 100644 --- a/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java +++ b/server/src/main/java/org/opensearch/identity/tokens/TokenManager.java @@ -15,33 +15,8 @@ public interface TokenManager { /** * Create a new auth token + * @param audience: The audience for the token * @return A new auth token */ - public AuthToken issueToken(); - - /** - * Validate an auth token based on the rules associated with its format - * @param token The token to validate - * @return True if the token is valid; False if the token is not valid - */ - public boolean validateToken(AuthToken token); - - /** - * Fetch the info from a token - * @param token The auth token to be parsed - * @return A String representing the info associated with the token - */ - public String getTokenInfo(AuthToken token); - - /** - * Revoke a token that should no longer be treated as valid - * @param token The Auth Token to be revoked - */ - public void revokeToken(AuthToken token); - - /** - * Updates a token to be valid for a greater period of time or to have different attributes. - * @param token The token to be refreshed - */ - public void resetToken(AuthToken token); + public AuthToken issueToken(String audience); }