diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/NativeSDKBasedFederatedOAuthClientResponse.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/NativeSDKBasedFederatedOAuthClientResponse.java new file mode 100644 index 00000000..707c0646 --- /dev/null +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/NativeSDKBasedFederatedOAuthClientResponse.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.application.authenticator.oidc; + +import org.apache.oltu.oauth2.client.response.OAuthClientResponse; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; + +/** + * This class holds the Native SDK based Federated OAuth Client response. + */ +public class NativeSDKBasedFederatedOAuthClientResponse extends OAuthClientResponse { + + public void setAccessToken(String accessToken) { + + this.parameters.put(OIDCAuthenticatorConstants.ACCESS_TOKEN, accessToken); + } + + public void setIdToken(String idToken) { + + this.parameters.put(OIDCAuthenticatorConstants.ID_TOKEN, idToken); + } + + + @Override + protected void setBody(String s) throws OAuthProblemException { + + } + + @Override + protected void setContentType(String s) { + + } + + @Override + protected void setResponseCode(int i) { + + } +} diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OIDCAuthenticatorConstants.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OIDCAuthenticatorConstants.java index cc3aad9d..e813df2d 100644 --- a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OIDCAuthenticatorConstants.java +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OIDCAuthenticatorConstants.java @@ -1,17 +1,17 @@ -/** - * Copyright (c) 2015, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +/* + * Copyright (c) 2015, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ @@ -20,8 +20,17 @@ import java.util.regex.Pattern; +/** + * This class holds the constants related to the OIDC authenticator. + */ public class OIDCAuthenticatorConstants { + public static final String STATE_PARAM = "state.param"; + public static final String ACCESS_TOKEN_PARAM = "accessToken"; + public static final String CODE_PARAM = "code.param"; + public static final String ID_TOKEN_PARAM = "idToken"; + public static final String SESSION_DATA_KEY_PARAM = "sessionDataKey"; + private OIDCAuthenticatorConstants() { } @@ -66,6 +75,9 @@ private OIDCAuthenticatorConstants() { public static final String PROMPT_TYPE = "prompt_type"; public static final String REDIRECTION_PROMPT = "REDIRECTION_PROMPT"; + /** + * This class holds the constants related to authenticator configuration parameters. + */ public class AuthenticatorConfParams { private AuthenticatorConfParams() { @@ -75,6 +87,9 @@ private AuthenticatorConfParams() { public static final String DEFAULT_IDP_CONFIG = "DefaultIdPConfig"; } + /** + * This class holds the constants related to IdP configuration parameters. + */ public class IdPConfParams { private IdPConfParams() { @@ -89,6 +104,9 @@ private IdPConfParams() { public static final String USER_INFO_EP = "UserInfoEndPoint"; } + /** + * This class holds the constants related to claims. + */ public class Claim { private Claim() { @@ -123,6 +141,9 @@ private Claim() { public static final String BACKCHANNEL_LOGOUT_EVENT_CLAIM = "{}"; } + /** + * This class holds the constants related to Back Channel Logout. + */ public class BackchannelLogout { private BackchannelLogout() { diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticator.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticator.java index 1c3de2bc..ce00c242 100644 --- a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticator.java +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticator.java @@ -1,23 +1,26 @@ -/** - * Copyright (c) 2013, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +/* + * Copyright (c) 2013, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.wso2.carbon.identity.application.authenticator.oidc; +import com.nimbusds.jose.JOSEException; import com.nimbusds.jose.util.JSONObjectUtils; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; import net.minidev.json.JSONArray; import org.apache.commons.codec.binary.Base64; import org.apache.commons.collections.MapUtils; @@ -39,6 +42,7 @@ import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator; import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorFlowStatus; import org.wso2.carbon.identity.application.authentication.framework.FederatedApplicationAuthenticator; +import org.wso2.carbon.identity.application.authentication.framework.config.model.ExternalIdPConfig; import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext; import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException; import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException; @@ -49,9 +53,14 @@ import org.wso2.carbon.identity.application.authenticator.oidc.internal.OpenIDConnectAuthenticatorDataHolder; import org.wso2.carbon.identity.application.authenticator.oidc.model.OIDCStateInfo; import org.wso2.carbon.identity.application.authenticator.oidc.util.OIDCErrorConstants.ErrorMessages; +import org.wso2.carbon.identity.application.authenticator.oidc.util.OIDCTokenValidationUtil; import org.wso2.carbon.identity.application.common.model.ClaimMapping; +import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig; +import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.application.common.model.IdentityProviderProperty; import org.wso2.carbon.identity.application.common.model.Property; import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; +import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil; import org.wso2.carbon.identity.base.IdentityConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LogConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; @@ -63,6 +72,10 @@ import org.wso2.carbon.identity.core.util.IdentityTenantUtil; import org.wso2.carbon.identity.core.util.IdentityUtil; import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.idp.mgt.IdentityProviderManagementException; +import org.wso2.carbon.idp.mgt.IdentityProviderManager; +import org.wso2.carbon.idp.mgt.util.IdPManagementConstants; import org.wso2.carbon.user.api.UserRealm; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.UserStoreManager; @@ -83,9 +96,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -93,19 +106,24 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REDIRECT_URL_SUFFIX; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.OIDC_FEDERATION_NONCE; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.ACCESS_TOKEN_PARAM; import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.AUTHENTICATOR_OIDC; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REQUIRED_PARAMS; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REDIRECT_URL; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.PROMPT_TYPE; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REDIRECTION_PROMPT; import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.Claim.NONCE; -import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.LogConstants.ActionIDs.PROCESS_AUTHENTICATION_RESPONSE; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.ID_TOKEN_PARAM; import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.LogConstants.ActionIDs.INITIATE_OUTBOUND_AUTH_REQUEST; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.LogConstants.ActionIDs.PROCESS_AUTHENTICATION_RESPONSE; import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.LogConstants.OUTBOUND_AUTH_OIDC_SERVICE; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.OIDC_FEDERATION_NONCE; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.PROMPT_TYPE; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REDIRECTION_PROMPT; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REDIRECT_URL; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REDIRECT_URL_SUFFIX; +import static org.wso2.carbon.identity.application.authenticator.oidc.OIDCAuthenticatorConstants.REQUIRED_PARAMS; import static org.wso2.carbon.identity.base.IdentityConstants.FEDERATED_IDP_SESSION_ID; +/** + * This class holds the OIDC authenticator. + */ public class OpenIDConnectAuthenticator extends AbstractApplicationAuthenticator implements FederatedApplicationAuthenticator { @@ -202,7 +220,8 @@ public boolean canHandle(HttpServletRequest request) { */ protected boolean isInitialRequest(AuthenticationContext context, HttpServletRequest request) { - return !context.isLogoutRequest() && !hasCodeParamInRequest(request) && !hasErrorParamInRequest(request); + return !context.isLogoutRequest() && !hasCodeParamInRequest(request) && !hasErrorParamInRequest(request) && + !isNativeSDKBasedFederationCall(request); } private boolean hasErrorParamInRequest(HttpServletRequest request) { @@ -253,7 +272,7 @@ protected String getLogoutUrl(Map authenticatorProperties) { } /** - * Returns the token endpoint of OIDC federated authenticator + * Returns the token endpoint of OIDC federated authenticator. * * @param authenticatorProperties Authentication properties configured in OIDC federated authenticator * configuration. @@ -719,6 +738,21 @@ protected OAuthClientResponse requestAccessToken(HttpServletRequest request, Aut throws AuthenticationFailedException { OAuthClientResponse oAuthResponse; + if (isTrustedTokenIssuer(context) && isNativeSDKBasedFederationCall(request)) { + String idToken = request.getParameter(ID_TOKEN_PARAM); + String accessToken = request.getParameter(ACCESS_TOKEN_PARAM); + try { + validateJWTToken(context, idToken); + } catch (ParseException | IdentityOAuth2Exception | JOSEException e) { + throw new AuthenticationFailedException("JWT Token validation Failed."); + } + NativeSDKBasedFederatedOAuthClientResponse nativeSDKBasedFederatedOAuthClientResponse + = new NativeSDKBasedFederatedOAuthClientResponse(); + nativeSDKBasedFederatedOAuthClientResponse.setAccessToken(accessToken); + nativeSDKBasedFederatedOAuthClientResponse.setIdToken(idToken); + + return nativeSDKBasedFederatedOAuthClientResponse; + } try { OAuthAuthzResponse authzResponse = OAuthAuthzResponse.oauthCodeAuthzResponse(request); OAuthClientRequest accessTokenRequest = getAccessTokenRequest(context, authzResponse); @@ -736,6 +770,86 @@ protected OAuthClientResponse requestAccessToken(HttpServletRequest request, Aut return oAuthResponse; } + private void validateJWTToken(AuthenticationContext context, String idToken) throws ParseException, + AuthenticationFailedException, JOSEException, IdentityOAuth2Exception { + + SignedJWT signedJWT = SignedJWT.parse(idToken); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + OIDCTokenValidationUtil.validateIssuerClaim(claimsSet); + String tenantDomain = context.getTenantDomain(); + String idpIdentifier = OIDCTokenValidationUtil.getIssuer(claimsSet); + IdentityProvider identityProvider = getIdentityProvider(idpIdentifier, tenantDomain); + + OIDCTokenValidationUtil.validateSignature(signedJWT, identityProvider); + OIDCTokenValidationUtil.validateAudience(claimsSet.getAudience(), identityProvider , tenantDomain); + } + + /** + * Get the identity provider from issuer and tenant domain. + * + * @param jwtIssuer JWT issuer. + * @param tenantDomain Tenant domain. + * @return IdentityProvider. + * @throws AuthenticationFailedException If there is an issue while getting the identity provider. + */ + private IdentityProvider getIdentityProvider(String jwtIssuer, String tenantDomain) + throws AuthenticationFailedException { + + IdentityProvider identityProvider; + ErrorMessages errorMessages = ErrorMessages.NO_REGISTERED_IDP_FOR_ISSUER; + try { + identityProvider = IdentityProviderManager.getInstance().getIdPByMetadataProperty( + IdentityApplicationConstants.IDP_ISSUER_NAME, jwtIssuer, tenantDomain, false); + + if (identityProvider == null) { + identityProvider = IdentityProviderManager.getInstance().getIdPByName(jwtIssuer, tenantDomain); + } + if (identityProvider != null && StringUtils.equalsIgnoreCase(identityProvider.getIdentityProviderName(), + OIDCAuthenticatorConstants.BackchannelLogout.DEFAULT_IDP_NAME)) { + // Check whether this jwt was issued by the resident identity provider. + identityProvider = getResidentIDPForIssuer(tenantDomain, jwtIssuer); + + if (identityProvider == null) { + throw new AuthenticationFailedException(errorMessages.getCode(), errorMessages.getMessage()); + } + } + } catch (IdentityProviderManagementException e) { + throw new AuthenticationFailedException(errorMessages.getCode(), errorMessages.getMessage(), e); + } + return identityProvider; + } + + /** + * Get the resident identity provider from issuer and tenant domain. + * + * @param tenantDomain Tenant domain. + * @param jwtIssuer Issuer of the jwt. + * @return IdentityProvider. + * @throws AuthenticationFailedException If there is an issue while getting the resident identity provider. + */ + private IdentityProvider getResidentIDPForIssuer(String tenantDomain, String jwtIssuer) + throws AuthenticationFailedException { + + String issuer = StringUtils.EMPTY; + IdentityProvider residentIdentityProvider; + try { + residentIdentityProvider = IdentityProviderManager.getInstance().getResidentIdP(tenantDomain); + } catch (IdentityProviderManagementException e) { + String errorMsg = ErrorMessages.GETTING_RESIDENT_IDP_FAILED.getCode() + " - " + + String.format(ErrorMessages.GETTING_RESIDENT_IDP_FAILED.getMessage(), tenantDomain); + throw new AuthenticationFailedException(errorMsg); + } + FederatedAuthenticatorConfig[] fedAuthnConfigs = residentIdentityProvider.getFederatedAuthenticatorConfigs(); + FederatedAuthenticatorConfig oauthAuthenticatorConfig = + IdentityApplicationManagementUtil.getFederatedAuthenticator(fedAuthnConfigs, + IdentityApplicationConstants.Authenticator.OIDC.NAME); + if (oauthAuthenticatorConfig != null) { + issuer = IdentityApplicationManagementUtil.getProperty(oauthAuthenticatorConfig.getProperties(), + OIDCAuthenticatorConstants.BackchannelLogout.OIDC_IDP_ENTITY_ID).getValue(); + } + return jwtIssuer.equals(issuer) ? residentIdentityProvider : null; + } + protected void processAuthenticatedUserScopes(AuthenticationContext context, String scopes) { if (LOG.isDebugEnabled()) { @@ -995,6 +1109,11 @@ public String getContextIdentifier(HttpServletRequest request) { if (LOG.isDebugEnabled()) { LOG.debug("Inside OpenIDConnectAuthenticator.getContextIdentifier()"); } + + if (isNativeSDKBasedFederationCall(request)) { + return request.getParameter(OIDCAuthenticatorConstants.SESSION_DATA_KEY_PARAM); + } + String state = request.getParameter(OIDCAuthenticatorConstants.OAUTH2_PARAM_STATE); if (state != null) { return state.split(",")[0]; @@ -1128,7 +1247,8 @@ public List getConfigurationProperties() { enableBasicAuth.setDisplayName("Enable HTTP basic auth for client authentication"); enableBasicAuth.setRequired(false); enableBasicAuth.setDescription( - "Specifies that HTTP basic authentication should be used for client authentication, else client credentials will be included in the request body"); + "Specifies that HTTP basic authentication should be used for client authentication, " + + "else client credentials will be included in the request body"); enableBasicAuth.setType("boolean"); enableBasicAuth.setDisplayOrder(10); configProperties.add(enableBasicAuth); @@ -1207,8 +1327,8 @@ protected String getSubjectFromUserIDClaimURI(AuthenticationContext context, Map String userIdClaimUriInOIDCDialect = null; if (useLocalClaimDialect) { if (StringUtils.isNotBlank(userIdClaimUri)) { - // User ID is defined in local claim dialect at the IDP. Find the corresponding OIDC claim and retrieve - // from idTokenClaims. + // User ID is defined in local claim dialect at the IDP. + // Find the corresponding OIDC claim and retrieve from idTokenClaims. userIdClaimUriInOIDCDialect = getUserIdClaimUriInOIDCDialect(userIdClaimUri, spTenantDomain); } else { if (LOG.isDebugEnabled()) { @@ -1481,4 +1601,32 @@ private static List getUserAttributeClaimMappingList(AuthenticatedUser a }) .collect(Collectors.toList()); } + + + private boolean isTrustedTokenIssuer(AuthenticationContext context) { + + ExternalIdPConfig externalIdPConfig = context.getExternalIdP(); + if (externalIdPConfig == null) { + return false; + } + + IdentityProvider externalIdentityProvider = externalIdPConfig.getIdentityProvider(); + if (externalIdentityProvider == null) { + return false; + } + + IdentityProviderProperty[] identityProviderProperties = externalIdentityProvider.getIdpProperties(); + for (IdentityProviderProperty identityProviderProperty: identityProviderProperties) { + if (identityProviderProperty.getName().equals(IdPManagementConstants.IS_TRUSTED_TOKEN_ISSUER)) { + return Boolean.parseBoolean(identityProviderProperty.getValue()); + } + } + + return false; + } + + private boolean isNativeSDKBasedFederationCall(HttpServletRequest request) { + + return request.getParameter(ACCESS_TOKEN_PARAM) != null && request.getParameter(ID_TOKEN_PARAM) != null; + } } diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCErrorConstants.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCErrorConstants.java index eaca65c2..af1e0fa0 100644 --- a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCErrorConstants.java +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCErrorConstants.java @@ -90,7 +90,13 @@ public enum ErrorMessages { "Error while terminating the sessions for the user: %s"), RETRIEVING_USER_ID_FAILED("OID-65014", "Error while retrieving user Id mapping for sub: %s"), - LOGOUT_SERVER_EXCEPTION("OID-65015", "Back channel logout failed due to server error"); + LOGOUT_SERVER_EXCEPTION("OID-65015", "Back channel logout failed due to server error"), + JWT_TOKEN_ISS_CLAIM_VALIDATION_FAILED( + "OID-65016", "Error while validating the iss claim in the jwt token"), + JWT_TOKEN_SIGNATURE_VALIDATION_FAILED("OID-65016", + "Error while validating the JWT token signature"), + JWT_TOKEN_AUD_CLAIM_VALIDATION_FAILED("OID-65017", + "Audience claim validation failed."); private final String code; private final String message; diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCTokenValidationUtil.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCTokenValidationUtil.java new file mode 100644 index 00000000..0e935456 --- /dev/null +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/main/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCTokenValidationUtil.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.application.authenticator.oidc.util; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException; +import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig; +import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.application.common.model.Property; +import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; +import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.util.JWTSignatureValidationUtils; +import org.wso2.carbon.idp.mgt.IdentityProviderManagementException; +import org.wso2.carbon.idp.mgt.IdentityProviderManager; + +import java.util.List; + +/** + * This class holds utilities related to OIDC token validation. + */ +public class OIDCTokenValidationUtil { + + private static final Log log = LogFactory.getLog(OIDCTokenValidationUtil.class); + + /** + * Get unique identifier to identify the identity provider. + * + * @param claimsSet claim set available in the logout token. + * @return unique idp identifier. + * @throws AuthenticationFailedException if there is an issue while getting the unique identifier. + */ + public static String getIssuer(JWTClaimsSet claimsSet) throws AuthenticationFailedException { + + return claimsSet.getIssuer(); + } + + /** + * Do the aud claim validation according to OIDC back-channel logout specification. + * + * @param audienceList - list containing audience values. + * @param idp - identity provider. + * @Param tenantDomain - the tenant domain + */ + public static void validateAudience(List audienceList, IdentityProvider idp, String tenantDomain) + throws AuthenticationFailedException { + + boolean audienceFound = false; + String tokenEndPointAlias = getTokenEndpointAlias(idp, tenantDomain); + for (String audience : audienceList) { + if (StringUtils.equals(tokenEndPointAlias, audience)) { + if (log.isDebugEnabled()) { + log.debug(tokenEndPointAlias + " of IDP was found in the list of audiences."); + } + audienceFound = true; + break; + } + } + if (!audienceFound) { + throw new AuthenticationFailedException ("None of the audience values matched the tokenEndpoint Alias " + + tokenEndPointAlias); + } + } + + /** + * Get token endpoint alias. + * + * @param identityProvider Identity provider + * @return token endpoint alias + */ + private static String getTokenEndpointAlias(IdentityProvider identityProvider, String tenantDomain) { + + Property oauthTokenURL = null; + String tokenEndPointAlias = null; + if (IdentityApplicationConstants.RESIDENT_IDP_RESERVED_NAME.equals( + identityProvider.getIdentityProviderName())) { + try { + identityProvider = IdentityProviderManager.getInstance().getResidentIdP(tenantDomain); + } catch (IdentityProviderManagementException e) { + if (log.isDebugEnabled()) { + log.debug("Error while getting Resident IDP :" + e.getMessage()); + } + } + FederatedAuthenticatorConfig[] fedAuthnConfigs = + identityProvider.getFederatedAuthenticatorConfigs(); + FederatedAuthenticatorConfig oauthAuthenticatorConfig = + IdentityApplicationManagementUtil.getFederatedAuthenticator(fedAuthnConfigs, + IdentityApplicationConstants.Authenticator.OIDC.NAME); + + if (oauthAuthenticatorConfig != null) { + oauthTokenURL = IdentityApplicationManagementUtil.getProperty( + oauthAuthenticatorConfig.getProperties(), + IdentityApplicationConstants.Authenticator.OIDC.OAUTH2_TOKEN_URL); + } + if (oauthTokenURL != null) { + tokenEndPointAlias = oauthTokenURL.getValue(); + if (log.isDebugEnabled()) { + log.debug("Token End Point Alias of Resident IDP :" + tokenEndPointAlias); + } + } + } else { + tokenEndPointAlias = identityProvider.getAlias(); + if (log.isDebugEnabled()) { + log.debug("Token End Point Alias of the Federated IDP: " + tokenEndPointAlias); + } + } + return tokenEndPointAlias; + } + + /** + * Validate the JWT signature. + * + * @param signedJWT singed JWT. + * @param identityProvider identity provider. + * @throws JOSEException if there is an issue while verifying the singed JWT. + * @throws IdentityOAuth2Exception if there is an issue while validating the signature. + */ + public static void validateSignature(SignedJWT signedJWT, + IdentityProvider identityProvider) throws JOSEException, + IdentityOAuth2Exception , AuthenticationFailedException { + + if (!JWTSignatureValidationUtils.validateSignature(signedJWT, identityProvider)) { + throw new AuthenticationFailedException(OIDCErrorConstants.ErrorMessages. + JWT_TOKEN_SIGNATURE_VALIDATION_FAILED.getCode(), + OIDCErrorConstants.ErrorMessages.JWT_TOKEN_SIGNATURE_VALIDATION_FAILED.getMessage()); + } + } + + /** + * Validate the issuer claim. + * + * @param claimsSet JWT claims set + * @throws AuthenticationFailedException if there is an issue while validating the issuer. + */ + public static void validateIssuerClaim(JWTClaimsSet claimsSet) throws AuthenticationFailedException { + + if (StringUtils.isBlank(getIssuer(claimsSet))) { + throw new AuthenticationFailedException(OIDCErrorConstants.ErrorMessages. + JWT_TOKEN_ISS_CLAIM_VALIDATION_FAILED.getCode(), + OIDCErrorConstants.ErrorMessages.JWT_TOKEN_ISS_CLAIM_VALIDATION_FAILED.getMessage()); + } + } +} diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticatorTest.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticatorTest.java index b030b480..959075d4 100644 --- a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticatorTest.java +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/OpenIDConnectAuthenticatorTest.java @@ -1,17 +1,17 @@ -/** - * Copyright (c) 2017, WSO2 LLC. (https://www.wso2.com) All Rights Reserved. +/* + * Copyright (c) 2017, WSO2 LLC. (http://www.wso2.com). * * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the + * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ @@ -50,6 +50,7 @@ import org.wso2.carbon.identity.application.authenticator.oidc.internal.OpenIDConnectAuthenticatorDataHolder; import org.wso2.carbon.identity.application.common.model.ClaimMapping; import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.application.common.model.IdentityProviderProperty; import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; import org.wso2.carbon.identity.central.log.mgt.utils.LoggerUtils; import org.wso2.carbon.identity.claim.metadata.mgt.ClaimMetadataManagementService; @@ -57,6 +58,7 @@ import org.wso2.carbon.identity.core.ServiceURLBuilder; import org.wso2.carbon.identity.core.util.IdentityCoreConstants; import org.wso2.carbon.identity.core.util.IdentityUtil; +import org.wso2.carbon.idp.mgt.util.IdPManagementConstants; import org.wso2.carbon.user.api.RealmConfiguration; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.core.UserRealm; @@ -97,11 +99,12 @@ AUTHENTICATOR_FRIENDLY_NAME; /*** - * Unit test class for OpenIDConnectAuthenticatorTest class. + * Unit test class for OpenIDConnectAuthenticator class. */ @PrepareForTest({LogFactory.class, OAuthClient.class, URL.class, FrameworkUtils.class, OpenIDConnectAuthenticatorDataHolder.class, OAuthAuthzResponse.class, OAuthClientRequest.class, - OAuthClientResponse.class, IdentityUtil.class, OpenIDConnectAuthenticator.class, ServiceURLBuilder.class, LoggerUtils.class}) + OAuthClientResponse.class, IdentityUtil.class, OpenIDConnectAuthenticator.class, ServiceURLBuilder.class, + LoggerUtils.class}) public class OpenIDConnectAuthenticatorTest extends PowerMockTestCase { @Mock @@ -152,6 +155,9 @@ public class OpenIDConnectAuthenticatorTest extends PowerMockTestCase { @Mock private ExternalIdPConfig externalIdPConfig; + @Mock + private IdentityProvider identityProvider; + @Mock private OpenIDConnectAuthenticatorDataHolder openIDConnectAuthenticatorDataHolder; @@ -176,6 +182,7 @@ public class OpenIDConnectAuthenticatorTest extends PowerMockTestCase { "oZxDH7JbIkPpKBe0cnYQWBxfHuGTUWhvnu629ek6v2YLkaHlb_Lm04xLD9FNxuZUNQFw83pQtDVpoX5r1V-F0DdUc7gA1RKN3" + "xMVYgRyfslRDveGYplxVVNQ1LU3lrZhgaTfcMEsC6rdbd1HjdzG71EPS4674HCSAUelOisNKGa2NgORpldDQsj376QD0G9Mhc8WtW" + "oguftrCCGjBy1kKT4VqFLOqlA-8wUhOj_rZT9SUIBQRDPu0RZobvsskqYo40GEZrUoa"; + private static String sessionDataKey = "7b1c8131-c6bd-4682-892e-1a948a9e57e8"; private static String nonce = "0ed8f1b3-e83f-46c0-8d52-f0d2e7925f98"; private static String invalidNonce = "7ed8f1b3-e83f-46c0-8d52-f0d2e7925f98"; private static OAuthClientResponse token; @@ -189,13 +196,16 @@ public void init() { openIDConnectAuthenticator = new OpenIDConnectAuthenticator(); authenticatorProperties = new HashMap<>(); authenticatorProperties.put("callbackUrl", "http://localhost:8080/playground2/oauth2client"); - authenticatorProperties.put(IdentityApplicationConstants.Authenticator.OIDC.QUERY_PARAMS, "scope=openid&state=OIDC&loginType=basic"); + authenticatorProperties.put(IdentityApplicationConstants.Authenticator.OIDC.QUERY_PARAMS, + "scope=openid&state=OIDC&loginType=basic"); authenticatorProperties.put(IdentityApplicationConstants.Authenticator.OIDC.SCOPES, "openid email profile"); authenticatorProperties.put("UserInfoUrl", "https://localhost:9443/oauth2/userinfo"); authenticatorProperties.put(OIDCAuthenticatorConstants.CLIENT_ID, "u5FIfG5xzLvBGiamoAYzzcqpBqga"); authenticatorProperties.put(OIDCAuthenticatorConstants.CLIENT_SECRET, "_kLtobqi08GytnypVW_Mmy1niAIa"); - authenticatorProperties.put(OIDCAuthenticatorConstants.OAUTH2_TOKEN_URL, "https://localhost:9443/oauth2/token"); - authenticatorProperties.put(OIDCAuthenticatorConstants.OAUTH2_AUTHZ_URL, "https://localhost:9443/oauth2/authorize"); + authenticatorProperties.put( + OIDCAuthenticatorConstants.OAUTH2_TOKEN_URL, "https://localhost:9443/oauth2/token"); + authenticatorProperties.put( + OIDCAuthenticatorConstants.OAUTH2_AUTHZ_URL, "https://localhost:9443/oauth2/authorize"); authenticatorProperties.put(IdentityApplicationConstants.Authenticator.SAML2SSO.IS_USER_ID_IN_CLAIMS, "true"); authenticatorParamProperties = new HashMap<>(); authenticatorParamProperties.put("username", "testUser"); @@ -229,7 +239,8 @@ public Object[][] getRequestStatus() { } @Test(dataProvider = "requestDataHandler") - public void testCanHandle(String grantType, String state, String loginType, String error, String expectedCanHandler, String expectedContext, String msgCanHandler, String msgContext) throws IOException { + public void testCanHandle(String grantType, String state, String loginType, String error, String expectedCanHandler, + String expectedContext, String msgCanHandler, String msgContext) throws IOException { mockStatic(LoggerUtils.class); when(LoggerUtils.isDiagnosticLogsEnabled()).thenReturn(true); @@ -238,7 +249,8 @@ public void testCanHandle(String grantType, String state, String loginType, Stri when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.LOGIN_TYPE)).thenReturn(loginType); when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.OAUTH2_ERROR)).thenReturn(error); - assertEquals(openIDConnectAuthenticator.canHandle(mockServletRequest), Boolean.parseBoolean(expectedCanHandler), msgCanHandler); + assertEquals(openIDConnectAuthenticator.canHandle(mockServletRequest), + Boolean.parseBoolean(expectedCanHandler), msgCanHandler); assertEquals(openIDConnectAuthenticator.getContextIdentifier(mockServletRequest), expectedContext, msgContext); } @@ -340,8 +352,10 @@ public void testGetSubjectAttributes() throws OAuthSystemException, public Object[][] getCommonAuthParams() { return new String[][]{ - // If condition : queryString != null && queryString.contains("scope=")&& queryString.contains("redirect_uri="). - {"scope=openid&state=OIDC&loginType=basic&redirect_uri=https://localhost:9443/redirect", "https://localhost:9443/redirect", "The redirect URI is invalid"}, + // If condition : + // queryString != null && queryString.contains("scope=") && queryString.contains("redirect_uri="). + {"scope=openid&state=OIDC&loginType=basic&redirect_uri=https://localhost:9443/redirect", + "https://localhost:9443/redirect", "The redirect URI is invalid"}, // If condition : queryString != null && queryString.contains("scope="). {"state=OIDC&loginType=basic&redirect_uri=https://localhost:9443/redirect", "https://localhost:9443/redirect", "The redirect URI is invalid"}, @@ -413,7 +427,16 @@ public void testInitiateAuthenticationRequestNullProperties() throws OAuthSystem public void testPassProcessAuthenticationResponse() throws Exception { setupTest(); + + IdentityProviderProperty property = new IdentityProviderProperty(); + property.setName(IdPManagementConstants.IS_TRUSTED_TOKEN_ISSUER); + property.setValue("false"); + IdentityProviderProperty[] identityProviderProperties = new IdentityProviderProperty[1]; + identityProviderProperties[0] = property; + when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); + when(externalIdPConfig.getIdentityProvider()).thenReturn(identityProvider); + when(identityProvider.getIdpProperties()).thenReturn(identityProviderProperties); when(openIDConnectAuthenticatorDataHolder.getClaimMetadataManagementService()).thenReturn (claimMetadataManagementService); when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); @@ -435,7 +458,16 @@ public void testPassProcessAuthenticationResponse() throws Exception { public void testPassProcessAuthenticationResponseWithNonce() throws Exception { setupTest(); + + IdentityProviderProperty property = new IdentityProviderProperty(); + property.setName(IdPManagementConstants.IS_TRUSTED_TOKEN_ISSUER); + property.setValue("false"); + IdentityProviderProperty[] identityProviderProperties = new IdentityProviderProperty[1]; + identityProviderProperties[0] = property; + when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); + when(externalIdPConfig.getIdentityProvider()).thenReturn(identityProvider); + when(identityProvider.getIdpProperties()).thenReturn(identityProviderProperties); when(openIDConnectAuthenticatorDataHolder.getClaimMetadataManagementService()).thenReturn (claimMetadataManagementService); when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); @@ -470,13 +502,23 @@ public void testPassProcessAuthenticationWithBlankCallBack() throws Exception { setupTest(); authenticatorProperties.put("callbackUrl", " "); mockStatic(IdentityUtil.class); - when(IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true)).thenReturn("http:/localhost:9443/oauth2/callback"); + when(IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true)) + .thenReturn("http:/localhost:9443/oauth2/callback"); mockStatic(LoggerUtils.class); when(LoggerUtils.isDiagnosticLogsEnabled()).thenReturn(true); setParametersForOAuthClientResponse(mockOAuthClientResponse, accessToken, idToken); when(openIDConnectAuthenticatorDataHolder.getClaimMetadataManagementService()).thenReturn (claimMetadataManagementService); + + IdentityProviderProperty property = new IdentityProviderProperty(); + property.setName(IdPManagementConstants.IS_TRUSTED_TOKEN_ISSUER); + property.setValue("false"); + IdentityProviderProperty[] identityProviderProperties = new IdentityProviderProperty[1]; + identityProviderProperties[0] = property; + when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); + when(externalIdPConfig.getIdentityProvider()).thenReturn(identityProvider); + when(identityProvider.getIdpProperties()).thenReturn(identityProviderProperties); whenNew(OAuthClient.class).withAnyArguments().thenReturn(mockOAuthClient); when(mockOAuthClient.accessToken(Matchers.anyObject())).thenReturn(mockOAuthJSONAccessTokenResponse); when(mockOAuthJSONAccessTokenResponse.getParam(anyString())).thenReturn(idToken); @@ -489,8 +531,18 @@ public void testFailProcessAuthenticationWhenNonceMisMatch() throws Exception { setupTest(); mockStatic(IdentityUtil.class); - when(IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true)).thenReturn("http:/localhost:9443/oauth2/callback"); + when(IdentityUtil.getServerURL(FrameworkConstants.COMMONAUTH, true, true)) + .thenReturn("http:/localhost:9443/oauth2/callback"); + + IdentityProviderProperty property = new IdentityProviderProperty(); + property.setName(IdPManagementConstants.IS_TRUSTED_TOKEN_ISSUER); + property.setValue("false"); + IdentityProviderProperty[] identityProviderProperties = new IdentityProviderProperty[1]; + identityProviderProperties[0] = property; + when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); + when(externalIdPConfig.getIdentityProvider()).thenReturn(identityProvider); + when(identityProvider.getIdpProperties()).thenReturn(identityProviderProperties); whenNew(OAuthClient.class).withAnyArguments().thenReturn(mockOAuthClient); when(mockOAuthClient.accessToken(any())).thenReturn(mockOAuthJSONAccessTokenResponse); when(mockAuthenticationContext.getProperty(OIDC_FEDERATION_NONCE)).thenReturn(invalidNonce); @@ -511,7 +563,16 @@ public void testPassProcessAuthenticationWithParamValue() throws Exception { setParametersForOAuthClientResponse(mockOAuthClientResponse, accessToken, idToken); when(openIDConnectAuthenticatorDataHolder.getClaimMetadataManagementService()).thenReturn (claimMetadataManagementService); + + IdentityProviderProperty property = new IdentityProviderProperty(); + property.setName(IdPManagementConstants.IS_TRUSTED_TOKEN_ISSUER); + property.setValue("false"); + IdentityProviderProperty[] identityProviderProperties = new IdentityProviderProperty[1]; + identityProviderProperties[0] = property; + when(mockAuthenticationContext.getExternalIdP()).thenReturn(externalIdPConfig); + when(externalIdPConfig.getIdentityProvider()).thenReturn(identityProvider); + when(identityProvider.getIdpProperties()).thenReturn(identityProviderProperties); whenNew(OAuthClient.class).withAnyArguments().thenReturn(mockOAuthClient); when(mockOAuthClient.accessToken(Matchers.anyObject())) .thenReturn(mockOAuthJSONAccessTokenResponse); @@ -563,7 +624,9 @@ public void testClaimMappingWithNullValues() { } @Test(dataProvider = "requestDataHandler") - public void testGetContextIdentifier(String grantType, String state, String loginType, String error, String expectedCanHandler, String expectedContext, String msgCanHandler, String msgContext) throws Exception { + public void testGetContextIdentifier(String grantType, String state, String loginType, String error, + String expectedCanHandler, String expectedContext, String msgCanHandler, + String msgContext) throws Exception { when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.OAUTH2_GRANT_TYPE_CODE)).thenReturn(grantType); when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.OAUTH2_PARAM_STATE)).thenReturn(state); @@ -574,6 +637,17 @@ public void testGetContextIdentifier(String grantType, String state, String logi } + @Test + public void testGetContextIdentifierForNativeSDKBasedFederation() throws Exception { + + when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.ACCESS_TOKEN_PARAM)).thenReturn(accessToken); + when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.ID_TOKEN_PARAM)).thenReturn(idToken); + when(mockServletRequest.getParameter(OIDCAuthenticatorConstants.SESSION_DATA_KEY_PARAM)) + .thenReturn(sessionDataKey); + + assertEquals(openIDConnectAuthenticator.getContextIdentifier(mockServletRequest), sessionDataKey); + } + @Test public void testGetFriendlyName() throws Exception { @@ -691,7 +765,8 @@ private void setupTest() throws Exception { when(mockRealmConfiguration.getUserStoreProperty(IdentityCoreConstants.MULTI_ATTRIBUTE_SEPARATOR)) .thenReturn(","); mockStatic(IdentityUtil.class); - when(IdentityUtil.getServerURL("", false, false)).thenReturn("https://localhost:9443"); + when(IdentityUtil.getServerURL("", false, false)) + .thenReturn("https://localhost:9443"); mockStatic(ServiceURLBuilder.class); when(ServiceURLBuilder.create()).thenReturn(serviceURLBuilder); diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCTokenValidationUtilTest.java b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCTokenValidationUtilTest.java new file mode 100644 index 00000000..00793857 --- /dev/null +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/java/org/wso2/carbon/identity/application/authenticator/oidc/util/OIDCTokenValidationUtilTest.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.identity.application.authenticator.oidc.util; + +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import org.mockito.Mock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.testng.PowerMockTestCase; +import org.testng.Assert; +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; +import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException; +import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.oauth2.util.JWTSignatureValidationUtils; + +import java.util.ArrayList; +import java.util.List; + +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.when; +import static org.powermock.api.mockito.PowerMockito.whenNew; + +/*** + * Unit test class for OIDCTokenValidationUtil class. + */ +@PrepareForTest({JWTSignatureValidationUtils.class}) +public class OIDCTokenValidationUtilTest extends PowerMockTestCase { + + @Mock + private IdentityProvider identityProvider; + + private static String idToken = "eyJ4NXQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5" + + "sWkRVMU9HRmtOakZpTVEiLCJraWQiOiJOVEF4Wm1NeE5ETXlaRGczTVRVMVpHTTBNekV6T0RKaFpXSTRORE5sWkRVMU9" + + "HRmtOakZpTVEiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsImF1ZCI6WyJ1NUZJZkc1eHpMdkJHaWFtb0FZenpjc" + + "XBCcWdhIl0sImF6cCI6InU1RklmRzV4ekx2QkdpYW1vQVl6emNxcEJxZ2EiLCJhdXRoX3RpbWUiOjE1MDY1NzYwODAsImlzcyI6" + + "Imh0dHBzOlwvXC9sb2NhbGhvc3Q6OTQ0M1wvb2F1dGgyXC90b2tlbiIsImV4cCI6MTUwNjU3OTY4NCwibm9uY2UiOiI" + + "wZWQ4ZjFiMy1lODNmLTQ2YzAtOGQ1Mi1mMGQyZTc5MjVmOTgiLCJpYXQiOjE1MDY1NzYwODQsInNpZCI6Ijg3MDZmNWR" + + "hLTU0ZmMtNGZiMC1iNGUxLTY5MDZmYTRiMDRjMiJ9.HopPYFs4lInXvGztNEkJKh8Kdy52eCGbzYy6PiVuM_BlCcGff3SHO" + + "oZxDH7JbIkPpKBe0cnYQWBxfHuGTUWhvnu629ek6v2YLkaHlb_Lm04xLD9FNxuZUNQFw83pQtDVpoX5r1V-F0DdUc7gA1RKN3" + + "xMVYgRyfslRDveGYplxVVNQ1LU3lrZhgaTfcMEsC6rdbd1HjdzG71EPS4674HCSAUelOisNKGa2NgORpldDQsj376QD0G9Mhc8WtW" + + "oguftrCCGjBy1kKT4VqFLOqlA-8wUhOj_rZT9SUIBQRDPu0RZobvsskqYo40GEZrUoa"; + private static String invlaidIdToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwI" + + "iwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + private static String idpIdentifier = "https://localhost:9443/oauth2/token"; + private static String tenantDomain = "carbon.super"; + private static String alias = "https://localhost:9444/oauth2/token"; + + @BeforeTest + public void init() throws Exception { + + } + @Test + public void testGetIssuer() throws Exception { + + SignedJWT signedJWT = SignedJWT.parse(idToken); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + + Assert.assertEquals(OIDCTokenValidationUtil.getIssuer(claimsSet), idpIdentifier); + } + + @Test + public void testPassValidateAudienceExternal() throws Exception { + + when(identityProvider.getIdentityProviderName()).thenReturn("Google"); + when(identityProvider.getAlias()).thenReturn(alias); + + List audienceList = new ArrayList<>(); + audienceList.add(alias); + + OIDCTokenValidationUtil.validateAudience(audienceList, identityProvider,tenantDomain); + } + + @Test(expectedExceptions = AuthenticationFailedException.class) + public void testFailValidateAudienceExternal() throws Exception { + + when(identityProvider.getIdentityProviderName()).thenReturn("Google"); + when(identityProvider.getAlias()).thenReturn(alias); + + List audienceList = new ArrayList<>(); + + OIDCTokenValidationUtil.validateAudience(audienceList, identityProvider,tenantDomain); + } + + @Test + public void testPassValidateSignature() throws Exception { + + SignedJWT signedJWT = SignedJWT.parse(idToken); + mockStatic(JWTSignatureValidationUtils.class); + when(JWTSignatureValidationUtils.validateSignature(signedJWT, identityProvider)).thenReturn(true); + + OIDCTokenValidationUtil.validateSignature(signedJWT, identityProvider); + } + + @Test(expectedExceptions = AuthenticationFailedException.class) + public void testFailValidateSignature() throws Exception { + + SignedJWT signedJWT = SignedJWT.parse(idToken); + mockStatic(JWTSignatureValidationUtils.class); + when(JWTSignatureValidationUtils.validateSignature(signedJWT, identityProvider)).thenReturn(false); + + OIDCTokenValidationUtil.validateSignature(signedJWT, identityProvider); + } + + @Test + public void testPassValidateIssuerClaim() throws Exception { + + SignedJWT signedJWT = SignedJWT.parse(idToken); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + + OIDCTokenValidationUtil.validateIssuerClaim(claimsSet); + } + + @Test(expectedExceptions = AuthenticationFailedException.class) + public void testFailValidateIssuerClaim() throws Exception { + + SignedJWT signedJWT = SignedJWT.parse(invlaidIdToken); + JWTClaimsSet claimsSet = signedJWT.getJWTClaimsSet(); + + OIDCTokenValidationUtil.validateIssuerClaim(claimsSet); + } +} diff --git a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/resources/testng.xml b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/resources/testng.xml index 35e19fb8..d362ff35 100644 --- a/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/resources/testng.xml +++ b/components/org.wso2.carbon.identity.application.authenticator.oidc/src/test/resources/testng.xml @@ -26,6 +26,11 @@ + + + + + diff --git a/pom.xml b/pom.xml index cce89be9..e432fe0b 100644 --- a/pom.xml +++ b/pom.xml @@ -304,7 +304,7 @@ ${project.version} - 5.25.386 + 5.25.400 1.0.0.wso2v3 2.4.7 3.0.0.wso2v2