Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code coverage of OBO Authentication #3428

Merged
merged 25 commits into from
Oct 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,6 @@
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 @@

} catch (WeakKeyException e) {
log.error("Cannot authenticate user with JWT because of ", e);
return null;

Check warning on line 191 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L191

Added line #L191 was not covered by tests
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.debug("Invalid or expired JWT token.", e);
Expand All @@ -211,17 +207,13 @@
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());

Check warning on line 215 in src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java

View check run for this annotation

Codecov / codecov/patch

src/main/java/org/opensearch/security/http/OnBehalfOfAuthenticator.java#L215

Added line #L215 was not covered by tests

return jwtToken;
}

Expand Down
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
Loading