Skip to content

Commit

Permalink
ACS-6121 MNT-24007 Use issuer URI from the IdP (#2250)
Browse files Browse the repository at this point in the history
(cherry picked from commit 582fc8e)
  • Loading branch information
pzhyland committed Oct 16, 2023
1 parent cd2b9b1 commit 0ebe7b2
Show file tree
Hide file tree
Showing 2 changed files with 92 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.util.ResourceRetriever;
import com.nimbusds.jwt.proc.ConfigurableJWTProcessor;
import com.nimbusds.oauth2.sdk.id.Issuer;
import com.nimbusds.openid.connect.sdk.op.OIDCProviderMetadata;

import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.IdentityServiceFacadeException;
Expand Down Expand Up @@ -88,15 +89,16 @@
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.core.converter.ClaimTypeConverter;
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.web.client.RestOperations;
Expand Down Expand Up @@ -361,12 +363,18 @@ private ClientRegistration.Builder createBuilder(OIDCProviderMetadata metadata)
.map(OIDCProviderMetadata::getAuthorizationEndpointURI)
.map(URI::toASCIIString)
.orElse(null);

final String issuerUri = Optional.of(metadata)
.map(OIDCProviderMetadata::getIssuer)
.map(Issuer::getValue)
.orElseGet(config::getIssuerUrl);

return ClientRegistration
.withRegistrationId("ids")
.authorizationUri(authUri)
.tokenUri(metadata.getTokenEndpointURI().toASCIIString())
.jwkSetUri(metadata.getJWKSetURI().toASCIIString())
.issuerUri(config.getIssuerUrl())
.issuerUri(issuerUri)
.authorizationGrantType(AuthorizationGrantType.PASSWORD);
}

Expand Down Expand Up @@ -551,6 +559,34 @@ private String requireValidJwkSetUri(ProviderDetails providerDetails)
}
}

static class JwtIssuerValidator implements OAuth2TokenValidator<Jwt>
{
private final String requiredIssuer;

public JwtIssuerValidator(String issuer)
{
this.requiredIssuer = requireNonNull(issuer, "issuer cannot be null");
}

@Override
public OAuth2TokenValidatorResult validate(Jwt token)
{
requireNonNull(token, "token cannot be null");
final Object issuer = token.getClaim(JwtClaimNames.ISS);
if (issuer != null && requiredIssuer.equals(issuer.toString()))
{
return OAuth2TokenValidatorResult.success();
}

final OAuth2Error error = new OAuth2Error(
OAuth2ErrorCodes.INVALID_TOKEN,
"The iss claim is not valid. Expected `%s` but got `%s`.".formatted(requiredIssuer, issuer),
"https://tools.ietf.org/html/rfc6750#section-3.1");
return OAuth2TokenValidatorResult.failure(error);
}

}

private static boolean isDefined(String value)
{
return value != null && !value.isBlank();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,20 @@
import static org.mockito.Mockito.when;

import java.util.Map;
import java.util.UUID;

import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtDecoderProvider;
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacadeFactoryBean.JwtIssuerValidator;
import org.junit.Test;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;

public class IdentityServiceFacadeFactoryBeanTest
{
private static final String EXPECTED_ISSUER = "expected-issuer";
@Test
public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
{
Expand All @@ -62,4 +67,53 @@ public void shouldCreateJwtDecoderWithoutIDSWhenPublicKeyIsProvided()
.containsEntry(USERNAME_CLAIM, "piotrek");
}

@Test
public void shouldFailWithNotMatchingIssuerURIs()
{
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);

final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer("different-issuer"));
assertThat(validationResult).isNotNull();
assertThat(validationResult.hasErrors()).isTrue();
assertThat(validationResult.getErrors()).hasSize(1);

final OAuth2Error error = validationResult.getErrors().iterator().next();
assertThat(error).isNotNull();
assertThat(error.getDescription()).contains(EXPECTED_ISSUER, "different-issuer");
}

@Test
public void shouldFailWithNullIssuerURI()
{
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);

final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer(null));
assertThat(validationResult).isNotNull();
assertThat(validationResult.hasErrors()).isTrue();
assertThat(validationResult.getErrors()).hasSize(1);

final OAuth2Error error = validationResult.getErrors().iterator().next();
assertThat(error).isNotNull();
assertThat(error.getDescription()).contains(EXPECTED_ISSUER, "null");
}

@Test
public void shouldSucceedWithMatchingIssuerURI()
{
final JwtIssuerValidator issuerValidator = new JwtIssuerValidator(EXPECTED_ISSUER);

final OAuth2TokenValidatorResult validationResult = issuerValidator.validate(tokenWithIssuer(EXPECTED_ISSUER));
assertThat(validationResult).isNotNull();
assertThat(validationResult.hasErrors()).isFalse();
assertThat(validationResult.getErrors()).isEmpty();
}

private Jwt tokenWithIssuer(String issuer)
{
return Jwt.withTokenValue(UUID.randomUUID().toString())
.issuer(issuer)
.header("JUST", "FOR TESTING")
.build();
}

}

0 comments on commit 0ebe7b2

Please sign in to comment.