Skip to content

Commit

Permalink
okr #914: tests for security package
Browse files Browse the repository at this point in the history
  • Loading branch information
clean-coder committed Jun 3, 2024
1 parent d2caa33 commit 67d2c12
Show file tree
Hide file tree
Showing 6 changed files with 386 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private JWSKeySelector<SecurityContext> fromTenant(String tenantId) {
.orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
}

private JWSKeySelector<SecurityContext> fromUri(String uri) {
JWSKeySelector<SecurityContext> fromUri(String uri) {
try {
return JWSAlgorithmFamilyJWSKeySelector.fromJWKSetURL(new URL(uri));
} catch (Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ private String toTenant(Jwt jwt) {

private JwtIssuerValidator fromTenant(String tenant) {
return this.tenantConfigProvider.getTenantConfigById(tenant).map(TenantConfigProvider.TenantConfig::issuerUrl)
.map(JwtIssuerValidator::new).orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
.map(this::createValidator).orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
}

JwtIssuerValidator createValidator(String issuer) {
return new JwtIssuerValidator(issuer);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ch.puzzle.okr.security;

import ch.puzzle.okr.multitenancy.TenantContext;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;

import static ch.puzzle.okr.multitenancy.TenantContext.DEFAULT_TENANT_ID;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;

public class AuthenticationEventsTest {

public static final String TENANT_FROM_TOKEN = "pitc";

@DisplayName("onSuccess() puts Token from AuthenticationSuccessEvent in TenantContext")
@Test
void onSuccessPutsTokenFromAuthenticationSuccessEventInTenantContext() {
// arrange
Jwt tokenMock = mock(Jwt.class);

AuthenticationSuccessEvent successEvent = new AuthenticationSuccessEvent(mock(Authentication.class));
when(successEvent.getAuthentication().getPrincipal()).thenReturn(tokenMock);

JwtHelper jwtHelperMock = mock(JwtHelper.class);
when(jwtHelperMock.getTenantFromToken(tokenMock)).thenReturn(TENANT_FROM_TOKEN);

// pre-assert
assertDefaultTenantIsInTenantContext();

// act
AuthenticationEvents authenticationEvents = new AuthenticationEvents(jwtHelperMock);
authenticationEvents.onSuccess(successEvent);

// assert
assertTenantFromTokenIsInTenantContext();
verifyGetTenantFromTokenIsCalledWithTokenFromAuthenticationSuccessEvent(jwtHelperMock, tokenMock);
}

private void assertDefaultTenantIsInTenantContext() {
assertEquals(DEFAULT_TENANT_ID, TenantContext.getCurrentTenant());
}

private void assertTenantFromTokenIsInTenantContext() {
assertEquals(TENANT_FROM_TOKEN, TenantContext.getCurrentTenant());
}

private void verifyGetTenantFromTokenIsCalledWithTokenFromAuthenticationSuccessEvent(JwtHelper jwtHelper,
Jwt token) {
verify(jwtHelper).getTenantFromToken(token);
}

}
147 changes: 147 additions & 0 deletions backend/src/test/java/ch/puzzle/okr/security/JwtHelperTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package ch.puzzle.okr.security;

import ch.puzzle.okr.exception.OkrResponseStatusException;
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.multitenancy.TenantConfigProvider;
import com.nimbusds.jwt.JWTClaimsSet;
import jakarta.persistence.EntityNotFoundException;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.oauth2.jwt.Jwt;

import java.text.ParseException;
import java.util.Map;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpStatus.BAD_REQUEST;

public class JwtHelperTest {

private static final String TOKEN_CLAIMS_KEY_FIRSTNAME = "given_name";
private static final String TOKEN_CLAIMS_KEY_LASTNAME = "family_name";
private static final String TOKEN_CLAIMS_KEY_EMAIL = "email";
private static final String HANS = "Hans";
private static final String MUSTER = "Muster";
private static final String EMAIL = "[email protected]";

private static final String TOKEN_CLAIMS_KEY_TENANT = "tenant";
private static final String PITC = "pitc";

@DisplayName("getUserFromJwt() extracts User data from Token")
@Test
void getUserFromJwtExtractsUserDataFromToken() {
// arrange
Jwt tokenWithUserDataMock = mock(Jwt.class);
when(tokenWithUserDataMock.getClaims()).thenReturn(Map.of( //
TOKEN_CLAIMS_KEY_FIRSTNAME, HANS, //
TOKEN_CLAIMS_KEY_LASTNAME, MUSTER, //
TOKEN_CLAIMS_KEY_EMAIL, EMAIL //
));

JwtHelper jwtHelper = new JwtHelper(null, //
TOKEN_CLAIMS_KEY_FIRSTNAME, TOKEN_CLAIMS_KEY_LASTNAME, TOKEN_CLAIMS_KEY_EMAIL);

// act
User userFromToken = jwtHelper.getUserFromJwt(tokenWithUserDataMock);

// assert
assertEquals(HANS, userFromToken.getFirstname());
assertEquals(MUSTER, userFromToken.getLastname());
assertEquals(EMAIL, userFromToken.getEmail());
}

@DisplayName("getUserFromJwt() throws Exception if Token not contains User data")
@Test
void getUserFromJwtThrowsExceptionIfTokenNotContainsUserData() {
// arrange
Jwt tokenWithNoUserDataMock = mock(Jwt.class);

JwtHelper jwtHelper = new JwtHelper(null, //
TOKEN_CLAIMS_KEY_FIRSTNAME, TOKEN_CLAIMS_KEY_LASTNAME, TOKEN_CLAIMS_KEY_EMAIL);

// act + assert
OkrResponseStatusException okrResponseStatusException = //
assertThrows(OkrResponseStatusException.class, () -> jwtHelper.getUserFromJwt(tokenWithNoUserDataMock));

// assert
assertEquals(BAD_REQUEST, okrResponseStatusException.getStatusCode());
}

@DisplayName("getTenantFromToken() returns Tenant if Tenant found in TenantConfigProvider")
@Test
void getTenantFromTokenReturnsTenantIfTenantFoundInTenantConfigProvider() {
// arrange
Jwt tokenMock = mock(Jwt.class);
when(tokenMock.getClaimAsString(TOKEN_CLAIMS_KEY_TENANT)).thenReturn(PITC);

TenantConfigProvider tenantConfigProviderMock = mock(TenantConfigProvider.class);
when(tenantConfigProviderMock.getTenantConfigById(PITC)).thenReturn(Optional.of( //
new TenantConfigProvider.TenantConfig(PITC, //
new String[] {}, "jwkSetUri", "issuerUrl", //
"clientId", null) //
));

JwtHelper jwtHelper = new JwtHelper(tenantConfigProviderMock, null, null, null);

// act
String tenantFromToken = jwtHelper.getTenantFromToken(tokenMock);

// assert
assertEquals(PITC, tenantFromToken);
}

@DisplayName("getTenantFromToken() throws Exception if Tenant not found in TenantConfigProvider")
@Test
void getTenantFromTokenThrowsExceptionIfTenantNotFoundInTenantConfigProvider() {
// arrange
Jwt tokenMock = mock(Jwt.class);
when(tokenMock.getClaimAsString(TOKEN_CLAIMS_KEY_TENANT)).thenReturn(PITC);

TenantConfigProvider emptyTenantConfigProviderMock = mock(TenantConfigProvider.class);

JwtHelper jwtHelper = new JwtHelper(emptyTenantConfigProviderMock, null, null, null);

// act + assert
assertThrows(EntityNotFoundException.class, () -> jwtHelper.getTenantFromToken(tokenMock));
}

@DisplayName("getTenantFromJWTClaimsSet() returns Tenant if Tenant found in TenantConfigProvider")
@Test
void getTenantFromJWTClaimsSetReturnsTenantIfTenantFoundInTenantConfigProvider() throws ParseException {
// arrange
JWTClaimsSet claimsSetMock = mock(JWTClaimsSet.class);
when(claimsSetMock.getStringClaim(TOKEN_CLAIMS_KEY_TENANT)).thenReturn(PITC);

TenantConfigProvider tenantConfigProviderWithDataMock = mock(TenantConfigProvider.class);
when(tenantConfigProviderWithDataMock.getTenantConfigById(PITC)).thenReturn(Optional.of( //
new TenantConfigProvider.TenantConfig(PITC, //
new String[] {}, "jwkSetUri", "issuerUrl", //
"clientId", null) //
));

JwtHelper jwtHelper = new JwtHelper(tenantConfigProviderWithDataMock, null, null, null);

// act
String tenantFromToken = jwtHelper.getTenantFromJWTClaimsSet(claimsSetMock);

// assert
assertEquals(PITC, tenantFromToken);
}

@DisplayName("getTenantFromJWTClaimsSet() throws Exception if ClaimSet can not be parsed")
@Test
void getTenantFromJWTClaimsSetThrowsExceptionIfClaimSetCanNotBeParsed() throws ParseException {
// arrange
JWTClaimsSet claimsSetMock = mock(JWTClaimsSet.class);
when(claimsSetMock.getStringClaim(TOKEN_CLAIMS_KEY_TENANT)).thenThrow(new ParseException("", 0));

JwtHelper jwtHelper = new JwtHelper(null, null, null, null);

// act + assert
assertThrows(RuntimeException.class, () -> jwtHelper.getTenantFromJWTClaimsSet(claimsSetMock));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package ch.puzzle.okr.security;

import ch.puzzle.okr.multitenancy.TenantConfigProvider;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.proc.JWSKeySelector;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jwt.JWTClaimsSet;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.security.Key;
import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class TenantJWSKeySelectorTest {
private static final String PITC = "pitc";
private static final String JWK_SET_URI = "jwkSetUri";
private static final String MOCK_ALGORITHM = "mock_algorithm";
private static final String UNKNOWN_TENANT = "unknown tenant";

@DisplayName("selectKeys() throws Exception if JwkSetUri not found in TenantConfigProvider")
@Test
void selectKeysThrowsExceptionIfTenantConfigIsNotFound() {
// arrange
JWTClaimsSet jwtClaimsSetMock = mock(JWTClaimsSet.class);
JWSHeader jwsHeaderMock = mock(JWSHeader.class);
SecurityContext securityContext = new SecurityContext() {
};

JwtHelper jwtHelperMock = mock(JwtHelper.class);
when(jwtHelperMock.getTenantFromJWTClaimsSet(jwtClaimsSetMock)).thenReturn(PITC);

TenantConfigProvider emptyTenantConfigProviderMock = mock(TenantConfigProvider.class);

// act + assert
TenantJWSKeySelector selector = new TenantJWSKeySelector(emptyTenantConfigProviderMock, jwtHelperMock);
IllegalArgumentException illegalArgumentException = assertThrows(IllegalArgumentException.class, //
() -> selector.selectKeys(jwsHeaderMock, jwtClaimsSetMock, securityContext));

assertEquals(UNKNOWN_TENANT, illegalArgumentException.getLocalizedMessage());
}

@DisplayName("selectKeys() return Key with Mock Algorithm if JwkSetUri is found in TenantConfigProvider")
@Test
void selectKeysReturnKeyWithMockAlgorithmIfJwkSetUriIsFound() throws KeySourceException {
// arrange
JWTClaimsSet jwtClaimsSetMock = mock(JWTClaimsSet.class);
JWSHeader jwsHeaderMock = mock(JWSHeader.class);
SecurityContext securityContext = new SecurityContext() {
};

JwtHelper jwtHelperMock = mock(JwtHelper.class);
when(jwtHelperMock.getTenantFromJWTClaimsSet(jwtClaimsSetMock)).thenReturn(PITC);

TenantConfigProvider tenantConfigProviderWithDataMock = mock(TenantConfigProvider.class);
when(tenantConfigProviderWithDataMock.getJwkSetUri(PITC)).thenReturn(Optional.of(JWK_SET_URI));

TenantJWSKeySelector selector = new TenantJWSKeySelector(tenantConfigProviderWithDataMock, jwtHelperMock) {

@Override
JWSKeySelector<SecurityContext> fromUri(String uri) {
return (jwsHeader, securityContext) -> List.of(new Key() {
@Override
public String getAlgorithm() {
return MOCK_ALGORITHM;
}

@Override
public String getFormat() {
return null;
}

@Override
public byte[] getEncoded() {
return new byte[0];
}
});
}
};

// act + assert
List<? extends Key> keys = selector.selectKeys(jwsHeaderMock, jwtClaimsSetMock, securityContext);

// assert
assertListContainsSingleKeyWithMockAlgorithm(keys);
}

void assertListContainsSingleKeyWithMockAlgorithm(List<? extends Key> keys) {
assertNotNull(keys);
assertEquals(1, keys.size());
Key key = keys.get(0);
assertEquals(MOCK_ALGORITHM, key.getAlgorithm());
}
}
Loading

0 comments on commit 67d2c12

Please sign in to comment.