Skip to content

Commit

Permalink
2.x Adapt to optimized IAS server API (#1361)
Browse files Browse the repository at this point in the history
  • Loading branch information
finkmanAtSap authored Nov 24, 2023
1 parent 4b3e42a commit ea528d2
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 125 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public void xsuaaTokenValidationFails_withIasCombiningValidator() {
ValidationResult result = tokenValidator.validate(token);
assertThat(result.isValid()).isFalse();
assertThat(result.getErrorDescription()).startsWith(
"Issuer is not trusted because issuer 'http://auth.com' doesn't match any of these domains '[myauth.com]' of the identity provider");
"Issuer http://auth.com was not a trusted domain or a subdomain of the trusted domains [myauth.com].");
}

@Test@Ignore("to be fixed")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ private OAuth2ServiceConfigurationBuilder createXsuaaConfigurationBuilder() {

private OAuth2ServiceConfigurationBuilder createIasConfigurationBuilder() {
return OAuth2ServiceConfigurationBuilder.forService(IAS)
.withDomains(SecurityTest.DEFAULT_DOMAIN)
.withDomains(securityIasTest.getWireMockServer().baseUrl())
.withClientId(SecurityTest.DEFAULT_CLIENT_ID);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import javax.servlet.Servlet;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
Expand Down Expand Up @@ -169,7 +170,7 @@ public OAuth2ServiceConfigurationBuilder getOAuth2ServiceConfigurationBuilderFro
String configurationResourceName) {
return VcapServicesParser.fromFile(configurationResourceName)
.getConfigurationBuilder()
.withDomains(URI.create(issuerUrl).getHost())
.withDomains(issuerUrl)
.withUrl(issuerUrl);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public void getJwtGeneratorFromFile_setsTestingDefaults() throws IOException {
public void setKeys_invalidPath_throwsException() {
assertThatThrownBy(() -> SecurityTestRule.getInstance(XSUAA)
.setKeys("doesNotExist", "doesNotExist"))
.isInstanceOf(RuntimeException.class);
.isInstanceOf(RuntimeException.class);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
/**
* SPDX-FileCopyrightText: 2018-2023 SAP SE or an SAP affiliate company and Cloud Security Client Java contributors
*<p>
* <p>
* SPDX-License-Identifier: Apache-2.0
*/
package com.sap.cloud.security.token.validation.validators;

import static com.sap.cloud.security.token.validation.ValidationResults.createInvalid;
import static com.sap.cloud.security.token.validation.ValidationResults.createValid;
import static com.sap.cloud.security.xsuaa.Assertions.assertNotEmpty;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;

import com.sap.cloud.security.config.Service;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sap.cloud.security.config.OAuth2ServiceConfiguration;
import com.sap.cloud.security.json.JsonParsingException;
import com.sap.cloud.security.token.Token;
import com.sap.cloud.security.token.validation.ValidationResult;
import com.sap.cloud.security.token.validation.Validator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Objects;
import java.util.regex.Pattern;

import static com.sap.cloud.security.token.validation.ValidationResults.createInvalid;
import static com.sap.cloud.security.token.validation.ValidationResults.createValid;
import static com.sap.cloud.security.xsuaa.Assertions.assertNotEmpty;

/**
* Validates that the jwt token is issued by a trust worthy identity provider.
Expand All @@ -37,6 +38,7 @@
* These checks are a prerequisite for using the `JwtSignatureValidator`.
*/
class JwtIssuerValidator implements Validator<Token> {
protected static final String HTTPS_SCHEME = "https://";
private final List<String> domains;
protected final Logger logger = LoggerFactory.getLogger(getClass());

Expand All @@ -55,55 +57,34 @@ class JwtIssuerValidator implements Validator<Token> {

@Override
public ValidationResult validate(Token token) {
String issuer = token.getIssuer();
if (token.getService().equals(Service.IAS) && !issuer.startsWith("http")) {
issuer = "https://" + issuer;
}
ValidationResult validationResult = validateUrl(issuer);
if (validationResult.isErroneous()) {
return validationResult;
String issuer;

try {
issuer = token.getIssuer();
} catch(JsonParsingException e) {
return createInvalid("Issuer validation can not be performed because token issuer claim was not a String value.");
}
return matchesTokenIssuerUrl(issuer);
}

private ValidationResult matchesTokenIssuerUrl(String issuer) {
URI issuerUri = URI.create(issuer);
if (issuerUri.getQuery() == null && issuerUri.getFragment() == null && issuerUri.getHost() != null) {
for (String d : domains) {
if (issuerUri.getHost().endsWith(d)) {
return createValid();
}
}
if (issuer == null || issuer.trim().isEmpty()) {
return createInvalid("Issuer validation can not be performed because token does not contain an issuer claim.");
}
return createInvalid(
"Issuer is not trusted because issuer '{}' doesn't match any of these domains '{}' of the identity provider.",
issuer, domains);
}

private ValidationResult validateUrl(String issuer) {
URI issuerUri;
String issuerUrl = issuer.startsWith(HTTPS_SCHEME) ? issuer : HTTPS_SCHEME + issuer;
try {
if (issuer == null || issuer.trim().isEmpty()) {
return createInvalid(
"Issuer validation can not be performed because Jwt token does not contain an issuer claim.");
}
if (!issuer.startsWith("http")) {
return createInvalid(
"Issuer is not trusted because issuer '{}' does not provide a valid URI (missing http scheme). Please contact your Identity Provider Administrator.",
issuer);
}
issuerUri = new URI(issuer);
if (issuerUri.getQuery() == null && issuerUri.getFragment() == null && issuerUri.getHost() != null) {
new URL(issuerUrl);
} catch (MalformedURLException e) {
return createInvalid("Issuer validation can not be performed because token issuer is not a valid URL suitable for https.");
}

String issuerDomain = issuerUrl.substring(HTTPS_SCHEME.length());
for(String d : domains) {
// a string that ends with .<trustedDomain> and contains 1-63 letters, digits or '-' before that for the subdomain
String validSubdomainPattern = String.format("^[a-zA-Z0-9-]{1,63}\\.%s$", Pattern.quote(d));
if(Objects.equals(d, issuerDomain) || issuerDomain.matches(validSubdomainPattern)) {
return createValid();
}
} catch (URISyntaxException e) {
logger.error(
"Error: issuer claim '{}' does not provide a valid URI: {}. Please contact your Identity Provider Administrator.",
issuer, e.getMessage(), e);
}
return createInvalid(
"Issuer is not trusted because issuer does not provide a valid URI. Please contact your Identity Provider Administrator.",
issuer);
}

return createInvalid("Issuer {} was not a trusted domain or a subdomain of the trusted domains {}.", issuer, domains);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private URI getJwksUri(Token token) throws OAuth2ServiceException {
}

if (isTenantIdCheckEnabled && !domain.equals("" + configuration.getUrl()) && token.getAppTid() == null) {
throw new IllegalArgumentException("OIDC token must provide a valid " + TokenClaims.SAP_GLOBAL_APP_TID + " header when issuer has a different domain than the url from the service credentials.");
throw new IllegalArgumentException("OIDC token must provide the " + TokenClaims.SAP_GLOBAL_APP_TID + " claim for tenant validation when issuer is not the same as the url from the service credentials.");
}


Expand Down
Loading

0 comments on commit ea528d2

Please sign in to comment.