Skip to content

Commit

Permalink
Code coverage of OBO Authentication (opensearch-project#3428)
Browse files Browse the repository at this point in the history
### Description
Code coverage of OBO Authentication
* Category (Enhancement, New feature, Bug fix, Test fix, Refactoring,
Maintenance, Documentation)
Enhancement

### Issues Resolved
* Resolve opensearch-project#3101

### Check List
- [x] New functionality includes testing
- [ ] New functionality has been documented
- [x] Commits are signed per the DCO using --signoff

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and
signing off your commits, please check
[here](https://github.com/opensearch-project/OpenSearch/blob/main/CONTRIBUTING.md#developer-certificate-of-origin).

---------

Signed-off-by: Ryan Liang <[email protected]>
Signed-off-by: Darshit Chanpura <[email protected]>
Co-authored-by: Darshit Chanpura <[email protected]>
  • Loading branch information
RyanL1997 and DarshitChanpura authored Oct 12, 2023
1 parent c285e87 commit 387c8a5
Show file tree
Hide file tree
Showing 3 changed files with 457 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,6 @@ private AuthCredentials extractCredentials0(final SecurityRequest request) {
return null;
}

if (jwtParser == null) {
log.error("Missing Signing Key. JWT authentication will not work");
return null;
}

String jwtToken = extractJwtFromHeader(request);
if (jwtToken == null) {
return null;
Expand Down Expand Up @@ -193,6 +188,7 @@ private AuthCredentials extractCredentials0(final SecurityRequest request) {

} catch (WeakKeyException e) {
log.error("Cannot authenticate user with JWT because of ", e);
return null;
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Invalid or expired JWT token.", e);
Expand All @@ -211,17 +207,13 @@ private String extractJwtFromHeader(SecurityRequest request) {
return null;
}

if (!BEARER.matcher(jwtToken).matches()) {
return null;
}

if (jwtToken.toLowerCase().contains(BEARER_PREFIX)) {
jwtToken = jwtToken.substring(jwtToken.toLowerCase().indexOf(BEARER_PREFIX) + BEARER_PREFIX.length());
} else {
if (!BEARER.matcher(jwtToken).matches() || !jwtToken.toLowerCase().contains(BEARER_PREFIX)) {
logDebug("No Bearer scheme found in header");
return null;
}

jwtToken = jwtToken.substring(jwtToken.toLowerCase().indexOf(BEARER_PREFIX) + BEARER_PREFIX.length());

return jwtToken;
}

Expand Down
172 changes: 142 additions & 30 deletions src/test/java/org/opensearch/security/authtoken/jwt/JwtVendorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,88 @@
import org.apache.cxf.rs.security.jose.jwk.JsonWebKey;
import org.apache.cxf.rs.security.jose.jws.JwsJwtCompactConsumer;
import org.apache.cxf.rs.security.jose.jwt.JwtToken;
import org.junit.Assert;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.opensearch.common.settings.Settings;
import org.opensearch.security.support.ConfigConstants;

import java.util.List;
import java.util.Optional;
import java.util.function.LongSupplier;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class JwtVendorTest {
private Appender mockAppender;
private ArgumentCaptor<LogEvent> logEventCaptor;

@Test
public void testCreateJwkFromSettingsThrowsException() {
Settings faultySettings = Settings.builder().put("key.someProperty", "badValue").build();

Exception thrownException = assertThrows(Exception.class, () -> new JwtVendor(faultySettings, null));

String expectedMessagePart = "An error occurred during the creation of Jwk: ";
assertTrue(thrownException.getMessage().contains(expectedMessagePart));
}

@Test
public void testJsonWebKeyPropertiesSetFromJwkSettings() throws Exception {
Settings settings = Settings.builder().put("jwt.key.key1", "value1").put("jwt.key.key2", "value2").build();

JsonWebKey jwk = JwtVendor.createJwkFromSettings(settings);

assertEquals("value1", jwk.getProperty("key1"));
assertEquals("value2", jwk.getProperty("key2"));
}

@Test
public void testJsonWebKeyPropertiesSetFromSettings() {
Settings jwkSettings = Settings.builder().put("key1", "value1").put("key2", "value2").build();

JsonWebKey jwk = new JsonWebKey();
for (String key : jwkSettings.keySet()) {
jwk.setProperty(key, jwkSettings.get(key));
}

assertEquals("value1", jwk.getProperty("key1"));
assertEquals("value2", jwk.getProperty("key2"));
}

@Test
public void testCreateJwkFromSettings() throws Exception {
Settings settings = Settings.builder().put("signing_key", "abc123").build();

JsonWebKey jwk = JwtVendor.createJwkFromSettings(settings);
Assert.assertEquals("HS512", jwk.getAlgorithm());
Assert.assertEquals("sig", jwk.getPublicKeyUse().toString());
Assert.assertEquals("abc123", jwk.getProperty("k"));
assertEquals("HS512", jwk.getAlgorithm());
assertEquals("sig", jwk.getPublicKeyUse().toString());
assertEquals("abc123", jwk.getProperty("k"));
}

@Test
public void testCreateJwkFromSettingsWithoutSigningKey() {
Settings settings = Settings.builder().put("jwt", "").build();
Throwable exception = Assert.assertThrows(RuntimeException.class, () -> {
Throwable exception = assertThrows(RuntimeException.class, () -> {
try {
JwtVendor.createJwkFromSettings(settings);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
Assert.assertEquals(
assertEquals(
"java.lang.Exception: Settings for signing key is missing. Please specify at least the option signing_key with a shared secret.",
exception.getMessage()
);
Expand All @@ -72,15 +122,15 @@ public void testCreateJwtWithRoles() throws Exception {
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt);
JwtToken jwt = jwtConsumer.getJwtToken();

Assert.assertEquals("cluster_0", jwt.getClaim("iss"));
Assert.assertEquals("admin", jwt.getClaim("sub"));
Assert.assertEquals("audience_0", jwt.getClaim("aud"));
Assert.assertNotNull(jwt.getClaim("iat"));
Assert.assertNotNull(jwt.getClaim("exp"));
Assert.assertEquals(expectedExp, jwt.getClaim("exp"));
assertEquals("cluster_0", jwt.getClaim("iss"));
assertEquals("admin", jwt.getClaim("sub"));
assertEquals("audience_0", jwt.getClaim("aud"));
assertNotNull(jwt.getClaim("iat"));
assertNotNull(jwt.getClaim("exp"));
assertEquals(expectedExp, jwt.getClaim("exp"));
EncryptionDecryptionUtil encryptionUtil = new EncryptionDecryptionUtil(claimsEncryptionKey);
Assert.assertEquals(expectedRoles, encryptionUtil.decrypt(jwt.getClaim("er").toString()));
Assert.assertNull(jwt.getClaim("br"));
assertEquals(expectedRoles, encryptionUtil.decrypt(jwt.getClaim("er").toString()));
assertNull(jwt.getClaim("br"));
}

@Test
Expand Down Expand Up @@ -111,20 +161,20 @@ public void testCreateJwtWithRoleSecurityMode() throws Exception {
JwsJwtCompactConsumer jwtConsumer = new JwsJwtCompactConsumer(encodedJwt);
JwtToken jwt = jwtConsumer.getJwtToken();

Assert.assertEquals("cluster_0", jwt.getClaim("iss"));
Assert.assertEquals("admin", jwt.getClaim("sub"));
Assert.assertEquals("audience_0", jwt.getClaim("aud"));
Assert.assertNotNull(jwt.getClaim("iat"));
Assert.assertNotNull(jwt.getClaim("exp"));
Assert.assertEquals(expectedExp, jwt.getClaim("exp"));
assertEquals("cluster_0", jwt.getClaim("iss"));
assertEquals("admin", jwt.getClaim("sub"));
assertEquals("audience_0", jwt.getClaim("aud"));
assertNotNull(jwt.getClaim("iat"));
assertNotNull(jwt.getClaim("exp"));
assertEquals(expectedExp, jwt.getClaim("exp"));
EncryptionDecryptionUtil encryptionUtil = new EncryptionDecryptionUtil(claimsEncryptionKey);
Assert.assertEquals(expectedRoles, encryptionUtil.decrypt(jwt.getClaim("er").toString()));
Assert.assertNotNull(jwt.getClaim("br"));
Assert.assertEquals(expectedBackendRoles, jwt.getClaim("br"));
assertEquals(expectedRoles, encryptionUtil.decrypt(jwt.getClaim("er").toString()));
assertNotNull(jwt.getClaim("br"));
assertEquals(expectedBackendRoles, jwt.getClaim("br"));
}

@Test
public void testCreateJwtWithBadExpiry() {
public void testCreateJwtWithNegativeExpiry() {
String issuer = "cluster_0";
String subject = "admin";
String audience = "audience_0";
Expand All @@ -134,14 +184,40 @@ public void testCreateJwtWithBadExpiry() {
Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build();
JwtVendor jwtVendor = new JwtVendor(settings, Optional.empty());

Throwable exception = Assert.assertThrows(RuntimeException.class, () -> {
Throwable exception = assertThrows(RuntimeException.class, () -> {
try {
jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, List.of(), true);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
Assert.assertEquals("java.lang.Exception: The expiration time should be a positive integer", exception.getMessage());
assertEquals("java.lang.Exception: The expiration time should be a positive integer", exception.getMessage());
}

@Test
public void testCreateJwtWithExceededExpiry() throws Exception {
String issuer = "cluster_0";
String subject = "admin";
String audience = "audience_0";
List<String> roles = List.of("IT", "HR");
List<String> backendRoles = List.of("Sales", "Support");
int expirySeconds = 900;
LongSupplier currentTime = () -> (long) 100;
String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16);
Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build();
JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime));

Throwable exception = assertThrows(RuntimeException.class, () -> {
try {
jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, backendRoles, true);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
assertEquals(
"java.lang.Exception: The provided expiration time exceeds the maximum allowed duration of 600 seconds",
exception.getMessage()
);
}

@Test
Expand All @@ -154,14 +230,14 @@ public void testCreateJwtWithBadEncryptionKey() {

Settings settings = Settings.builder().put("signing_key", "abc123").build();

Throwable exception = Assert.assertThrows(RuntimeException.class, () -> {
Throwable exception = assertThrows(RuntimeException.class, () -> {
try {
new JwtVendor(settings, Optional.empty()).createJwt(issuer, subject, audience, expirySeconds, roles, List.of(), true);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
Assert.assertEquals("java.lang.IllegalArgumentException: encryption_key cannot be null", exception.getMessage());
assertEquals("java.lang.IllegalArgumentException: encryption_key cannot be null", exception.getMessage());
}

@Test
Expand All @@ -175,13 +251,49 @@ public void testCreateJwtWithBadRoles() {
Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build();
JwtVendor jwtVendor = new JwtVendor(settings, Optional.empty());

Throwable exception = Assert.assertThrows(RuntimeException.class, () -> {
Throwable exception = assertThrows(RuntimeException.class, () -> {
try {
jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, List.of(), true);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
Assert.assertEquals("java.lang.Exception: Roles cannot be null", exception.getMessage());
assertEquals("java.lang.Exception: Roles cannot be null", exception.getMessage());
}

@Test
public void testCreateJwtLogsCorrectly() throws Exception {
mockAppender = mock(Appender.class);
logEventCaptor = ArgumentCaptor.forClass(LogEvent.class);
when(mockAppender.getName()).thenReturn("MockAppender");
when(mockAppender.isStarted()).thenReturn(true);
Logger logger = (Logger) LogManager.getLogger(JwtVendor.class);
logger.addAppender(mockAppender);
logger.setLevel(Level.DEBUG);

// Mock settings and other required dependencies
LongSupplier currentTime = () -> (long) 100;
String claimsEncryptionKey = RandomStringUtils.randomAlphanumeric(16);
Settings settings = Settings.builder().put("signing_key", "abc123").put("encryption_key", claimsEncryptionKey).build();

String issuer = "cluster_0";
String subject = "admin";
String audience = "audience_0";
List<String> roles = List.of("IT", "HR");
List<String> backendRoles = List.of("Sales", "Support");
int expirySeconds = 300;

JwtVendor jwtVendor = new JwtVendor(settings, Optional.of(currentTime));

jwtVendor.createJwt(issuer, subject, audience, expirySeconds, roles, backendRoles, false);

verify(mockAppender, times(1)).append(logEventCaptor.capture());

LogEvent logEvent = logEventCaptor.getValue();
String logMessage = logEvent.getMessage().getFormattedMessage();
assertTrue(logMessage.startsWith("Created JWT:"));

String[] parts = logMessage.split("\\.");
assertTrue(parts.length >= 3);
}
}
Loading

0 comments on commit 387c8a5

Please sign in to comment.