diff --git a/build.gradle b/build.gradle index c89d8a6..c09c936 100644 --- a/build.gradle +++ b/build.gradle @@ -4,11 +4,11 @@ plugins { } group 'at.klausbetz' -version '1.11.0' +version '1.12.0' java.sourceCompatibility = JavaVersion.VERSION_11 ext { - keycloakVersion = '23.0.3' + keycloakVersion = '24.0.5' } repositories { diff --git a/src/main/java/at/klausbetz/provider/AppleIdentityProvider.java b/src/main/java/at/klausbetz/provider/AppleIdentityProvider.java index 7c4c27a..dade4c1 100644 --- a/src/main/java/at/klausbetz/provider/AppleIdentityProvider.java +++ b/src/main/java/at/klausbetz/provider/AppleIdentityProvider.java @@ -11,6 +11,7 @@ import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.common.util.Base64; @@ -85,8 +86,8 @@ protected BrokeredIdentityContext exchangeExternalImpl(EventBuilder event, Multi throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "token not set", Response.Status.BAD_REQUEST); } - if (OAuth2Constants.JWT_TOKEN_TYPE.equals(exchangeParams.getSubjectTokenType()) || OAuth2Constants.ID_TOKEN_TYPE.equals(exchangeParams.getSubjectTokenType())) { - var context = validateJwt(event, exchangeParams.getSubjectToken(), exchangeParams.getSubjectTokenType()); + if (OAuth2Constants.ID_TOKEN_TYPE.equals(exchangeParams.getSubjectTokenType())) { + var context = validateAppleIdToken(event, exchangeParams.getSubjectToken()); return exchangeParams.getUserJson() != null ? handleUserJson(context, exchangeParams.getUserJson()) : context; } else if (APPLE_AUTHZ_CODE.equals(exchangeParams.getSubjectTokenType())) { return exchangeAuthorizationCode(exchangeParams.getSubjectToken(), exchangeParams.getUserJson(), exchangeParams.getAppIdentifier()); @@ -97,6 +98,52 @@ protected BrokeredIdentityContext exchangeExternalImpl(EventBuilder event, Multi } } + // NOTE: slightly modified version of OIDCIdentityProvider.validateJWT + private BrokeredIdentityContext validateAppleIdToken(EventBuilder event, String subjectToken) { + event.detail("validation_method", "signature"); + if (getConfig().isUseJwksUrl()) { + if (getConfig().getJwksUrl() == null) { + event.detail(Details.REASON, "jwks url unset"); + event.error(Errors.INVALID_CONFIG); + throw new ErrorResponseException(Errors.INVALID_CONFIG, "Invalid server config", Response.Status.BAD_REQUEST); + } + } else if (getConfig().getPublicKeySignatureVerifier() == null) { + event.detail(Details.REASON, "public key unset"); + event.error(Errors.INVALID_CONFIG); + throw new ErrorResponseException(Errors.INVALID_CONFIG, "Invalid server config", Response.Status.BAD_REQUEST); + } + + JsonWebToken parsedToken; + try { + parsedToken = validateToken(subjectToken, true); + } catch (IdentityBrokerException e) { + logger.debug("Unable to validate token for exchange", e); + event.detail(Details.REASON, "token validation failure"); + event.error(Errors.INVALID_TOKEN); + throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "invalid token", Response.Status.BAD_REQUEST); + } + + try { + BrokeredIdentityContext context = extractIdentity(null, null, parsedToken); + if (context == null) { + event.detail(Details.REASON, "Failed to extract identity from token"); + event.error(Errors.INVALID_TOKEN); + throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "invalid token", Response.Status.BAD_REQUEST); + } + + context.getContextData().put(VALIDATED_ID_TOKEN, parsedToken); + context.getContextData().put(EXCHANGE_PROVIDER, getConfig().getAlias()); + context.setIdp(this); + context.setIdpConfig(getConfig()); + return context; + } catch (IOException e) { + logger.debug("Unable to extract identity from identity token", e); + event.detail(Details.REASON, "Unable to extract identity from identity token: " + e.getMessage()); + event.error(Errors.INVALID_TOKEN); + throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "invalid token", Response.Status.BAD_REQUEST); + } + } + public void prepareClientSecret(String clientId) { if (!isValidSecret(getConfig().getClientSecret())) { getConfig().setClientSecret(generateJWS(