-
Notifications
You must be signed in to change notification settings - Fork 135
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
911aef5
commit 9389804
Showing
5 changed files
with
140 additions
and
111 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,6 @@ | |
import com.sap.cloud.security.config.Service; | ||
import com.sap.cloud.security.token.SapIdToken; | ||
import com.sap.cloud.security.token.Token; | ||
import com.sap.cloud.security.token.TokenClaims; | ||
import com.sap.cloud.security.token.validation.ValidationResult; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.Test; | ||
|
@@ -20,24 +19,23 @@ | |
|
||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collections; | ||
|
||
import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
import static org.hamcrest.CoreMatchers.is; | ||
import static org.hamcrest.CoreMatchers.startsWith; | ||
import static org.hamcrest.MatcherAssert.assertThat; | ||
import static org.mockito.Mockito.when; | ||
|
||
class JwtIssuerValidatorTest { | ||
|
||
private JwtIssuerValidator cut; | ||
private Token token; | ||
private final String[] domains = new String[] { "customer.ondemand.com", "accounts400.ondemand.com" }; | ||
private final String[] trustedDomains = new String[] { "customer.ondemand.com", "accounts400.ondemand.com" }; | ||
|
||
@BeforeEach | ||
void setup() { | ||
cut = new JwtIssuerValidator(Arrays.asList(domains)); | ||
cut = new JwtIssuerValidator(Arrays.asList(trustedDomains)); | ||
token = Mockito.mock(SapIdToken.class); | ||
when(token.getService()).thenReturn(Service.IAS); | ||
} | ||
|
||
@Test | ||
|
@@ -49,84 +47,133 @@ void constructor_throwsOnNullValues() { | |
.hasMessageContainingAll("JwtIssuerValidator", "domain(s)"); | ||
} | ||
|
||
@Test | ||
void validationFails_whenIssuerDomainDoesNotMatchIdentityProviderDomains() { | ||
configureMock("https://otherdomain.test.ondemand.com", null); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
} | ||
|
||
@ParameterizedTest | ||
@NullAndEmptySource | ||
@ValueSource(strings = { " " }) | ||
void validationIgnoresEmptyIssuer_whenIasIssuerIsGiven(String issuer) { | ||
cut = new JwtIssuerValidator(Collections.singletonList("accounts400.ondemand.com")); | ||
configureMock(issuer, "https://test.accounts400.ondemand.com"); | ||
@CsvSource({ | ||
"accounts400.ondemand.com", | ||
"https://accounts400.ondemand.com", | ||
"tenant.accounts400.ondemand.com", | ||
"https://tenant.accounts400.ondemand.com", | ||
"tenant-0815WithNumbers.accounts400.ondemand.com", | ||
"https://tenant-0815WithNumbers.accounts400.ondemand.com" | ||
}) | ||
void validationSucceeds_forValidIssuers(String issuer) { | ||
when(token.getIssuer()).thenReturn(issuer); | ||
|
||
ValidationResult validationResult = cut.validate(token); | ||
assertThat(validationResult.isValid(), is(true)); | ||
} | ||
|
||
@Test | ||
void validationSucceeds_whenIasIssuerIsEmptyOrNull() { | ||
cut = new JwtIssuerValidator(Collections.singletonList("accounts400.ondemand.com")); | ||
configureMock("https://test.accounts400.ondemand.com", null); | ||
|
||
ValidationResult validationResult = cut.validate(token); | ||
assertThat(validationResult.isErroneous(), is(false)); | ||
} | ||
|
||
@Test | ||
void validationFails_withoutMatchingIasIssuer() { | ||
configureMock("https://otherDomain.accounts400.ondemand.com", "https://iasDomain.accounts.ondemand.com"); | ||
void validationFails_whenSubdomainHasMoreThan63Characters() { | ||
for(String d : trustedDomains) { | ||
when(token.getIssuer()).thenReturn("https://a." + d); | ||
assertThat(cut.validate(token).isValid(), is(true)); | ||
|
||
ValidationResult validationResult = cut.validate(token); | ||
assertThat(validationResult.isErroneous(), is(true)); | ||
assertThat(validationResult.getErrorDescription(), startsWith( | ||
"Issuer is not trusted because issuer 'https://iasDomain.accounts.ondemand.com' doesn't match any of these domains '[customer.ondemand.com, accounts400.ondemand.com]' of the identity provider.")); | ||
when(token.getIssuer()).thenReturn("https://" + "a".repeat(63) + "." + d); | ||
assertThat(cut.validate(token).isValid(), is(true)); | ||
|
||
when(token.getIssuer()).thenReturn("https://" + "a".repeat(64) + "." + d); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
} | ||
} | ||
|
||
@Test | ||
void validationIgnoresInvalidIssuer_whenIasIssuerIsGiven() { | ||
cut = new JwtIssuerValidator(Arrays.asList(domains)); | ||
configureMock("invalid_url", "https://otherDomain.accounts400.ondemand.com"); | ||
@ParameterizedTest | ||
@CsvSource({ | ||
"https://accou\tnts400.ondemand.com", | ||
"https://accou\nnts400.ondemand.com", | ||
"https://accounts400.onde\tmand.com", | ||
"https://accounts400.onde\nmand.com", | ||
"https://tena\tnt.accounts400.ondemand.com", | ||
"https://tena\nnt.accounts400.ondemand.com", | ||
"https://tenant.accounts400.onde\tmand.com", | ||
"https://tenant.accounts400.onde\nmand.com" | ||
}) | ||
void validationFails_whenIssuerContainsInvisibleCharacters(String issuer) { | ||
when(token.getIssuer()).thenReturn(issuer); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
} | ||
|
||
ValidationResult validationResult = cut.validate(token); | ||
assertThat(validationResult.isErroneous(), is(false)); | ||
@ParameterizedTest | ||
@CsvSource({ | ||
"https://accounts400%2eondemand.com", | ||
"https://accounts400.ondemand.com%2eattackerdomain.com", | ||
"https://tenant%2eaccounts400.ondemand.com", | ||
"https://attackerdomain.com%2eaccounts400.ondemand.com", | ||
"tenant%2d0815WithNumbers.accounts400.ondemand.com", | ||
}) | ||
void validationFails_whenIssuerContainsEncodedCharacters(String issuer) { | ||
when(token.getIssuer()).thenReturn(issuer); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ "https://subdomain.accounts400.ondemand.com#anyFragment_keys", | ||
"https://subdomain.accounts400.ondemand.com?a=b", | ||
"\0://myauth.com", | ||
"https://otherDomain.org?accounts400.ondemand.com", }) | ||
void validationFails_iasIssuerUrl(String iasIssuer) { | ||
cut = new JwtIssuerValidator(Arrays.asList(domains)); | ||
configureMock("https://otherDomain.accounts400.ondemand.com", iasIssuer); | ||
@NullAndEmptySource | ||
@ValueSource(strings = { " " }) | ||
void validationFails_whenIssuerIsEmpty(String issuer) { | ||
when(token.getIssuer()).thenReturn(issuer); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
} | ||
|
||
ValidationResult validationResult = cut.validate(token); | ||
assertThat(validationResult.isErroneous(), is(true)); | ||
assertThat(validationResult.getErrorDescription(), startsWith("Issuer is not trusted because issuer ")); | ||
@Test | ||
void validationFails_whenIssuerIsNotAValidURL() { | ||
when(token.getIssuer()).thenReturn("https://"); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
|
||
when(token.getIssuer()).thenReturn("http://"); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
|
||
when(token.getIssuer()).thenReturn("http://" + trustedDomains[0]); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
} | ||
|
||
@ParameterizedTest | ||
@CsvSource({ "https://otherDomain.accounts400.ondemand.com,", | ||
"https://paas.accounts400.ondemand.com,", | ||
"https://nestle.com,paas.accounts400.ondemand.com,", | ||
"subdomain.accounts400.ondemand.com,", | ||
"https://nestle.com,https://paas.accounts400.ondemand.com," }) | ||
void validationSucceeds(String issuer, String iasIssuer) { | ||
cut = new JwtIssuerValidator(Arrays.asList(domains)); | ||
configureMock(issuer, iasIssuer); | ||
|
||
ValidationResult validationResult = cut.validate(token); | ||
assertThat(validationResult.isValid(), is(true)); | ||
@CsvSource({ | ||
"https://accounts400.ondemand.coma", | ||
"https://accounts400.ondemand.com0", | ||
"https://accounts400.ondemand.com/", | ||
"https://accounts400.ondemand.com/path", | ||
"https://accounts400.ondemand.com%2f", | ||
"https://accounts400.ondemand.com%2fpath", | ||
"https://accounts400.ondemand.com&", | ||
"https://accounts400.ondemand.com%26", | ||
"https://accounts400.ondemand.com?", | ||
"https://accounts400.ondemand.com?foo", | ||
"https://accounts400.ondemand.com?foo=bar", | ||
"https://accounts400.ondemand.com%3f", | ||
"https://accounts400.ondemand.com%3ffoo", | ||
"https://accounts400.ondemand.com%3ffoo=bar", | ||
"https://accounts400.ondemand.com#", | ||
"https://accounts400.ondemand.com#foo", | ||
"https://accounts400.ondemand.com%23", | ||
"https://accounts400.ondemand.com%23foo", | ||
"https://[email protected]", | ||
"https://user%40accounts400.ondemand.com", | ||
}) | ||
void validationFails_whenIssuerContainsMoreThanDomain(String issuer) { | ||
when(token.getIssuer()).thenReturn(issuer); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
} | ||
|
||
private void configureMock(String issuer, String iasIssuer) { | ||
when(token.getService()).thenReturn(Service.IAS); | ||
when(token.getIssuer()).thenCallRealMethod(); | ||
when(token.getClaimAsString(TokenClaims.ISSUER)).thenReturn(issuer); | ||
when(token.getClaimAsString(TokenClaims.IAS_ISSUER)).thenReturn(iasIssuer); | ||
when(token.hasClaim("ias_iss")).thenReturn(iasIssuer != null); | ||
@ParameterizedTest | ||
@CsvSource({ | ||
"https://attackerdomain.com", | ||
"https://tenant.attackerdomain.com", | ||
"https://myaccounts400.ondemand.com", | ||
"https://accounts400.ondemand.com.attackerDomain.com", | ||
"https://accounts400.ondemand.com%2eattackerDomain.com", | ||
"https://accounts400.ondemand.com%2dattackerDomain.com", | ||
}) | ||
void validationFails_whenIssuerIsNotASubdomainOfTrustedDomains(String issuer) { | ||
when(token.getIssuer()).thenReturn(issuer); | ||
assertThat(cut.validate(token).isValid(), is(false)); | ||
assertThat(cut.validate(token).isErroneous(), is(true)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters