Skip to content

Commit

Permalink
improve the code to include hybrid flow response type
Browse files Browse the repository at this point in the history
  • Loading branch information
asha15 committed Jun 26, 2024
1 parent 15fb88a commit 9a7c756
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,8 @@ public static class OIDCConfigProperties {
public static final String BACK_CHANNEL_LOGOUT_URL = "backChannelLogoutURL";
public static final String FRONT_CHANNEL_LOGOUT_URL = "frontchannelLogoutURL";
public static final String TOKEN_TYPE = "tokenType";
public static final String HYBRID_FLOW_ENABLED = "hybridFlowEnabled";
public static final String HYBRID_FLOW_RESPONSE_TYPE = "hybridFlowResponseType";
public static final String BYPASS_CLIENT_CREDENTIALS = "bypassClientCredentials";
public static final String RENEW_REFRESH_TOKEN = "renewRefreshToken";
public static final String TOKEN_BINDING_TYPE = "tokenBindingType";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.wso2.carbon.identity.oauth.common.OAuth2ErrorCodes;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration;
import org.wso2.carbon.identity.oauth.dao.OAuthAppDO;
import org.wso2.carbon.identity.oauth.endpoint.util.EndpointUtil;
import org.wso2.carbon.identity.oauth.endpoint.util.TestOAuthEndpointBase;
import org.wso2.carbon.identity.oauth.par.core.OAuthParRequestWrapper;
Expand All @@ -62,6 +63,7 @@
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
Expand Down Expand Up @@ -365,6 +367,13 @@ public void testPar(Object requestParamsObj, Object paramMapObj, Object oAuthCli

HttpServletRequest request = mockHttpRequest(requestParams, new HashMap<>());

if (Objects.equals(request.getParameter(OAuthConstants.OAuth20Params.RESPONSE_TYPE),
RESPONSE_TYPE_CODE_ID_TOKEN)) {
OAuthAppDO oauthAppDO = OAuth2Util.getAppInformationByClientId(CLIENT_ID_VALUE);
oauthAppDO.setHybridFlowEnabled(true);
oauthAppDO.setHybridFlowResponseType(RESPONSE_TYPE_CODE_ID_TOKEN);
}

// Set authenticated client context
request.setAttribute(OAuthConstants.CLIENT_AUTHN_CONTEXT, oAuthClientAuthnContext);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@
<xs:element minOccurs="0" name="fapiConformanceEnabled" type="xs:boolean"/>
<xs:element minOccurs="0" name="frontchannelLogoutUrl" nillable="true" type="xs:string"/>
<xs:element minOccurs="0" name="grantTypes" nillable="true" type="xs:string"/>
<xs:element minOccurs="0" name="hybridFlowEnabled" type="xs:boolean"/>
<xs:element minOccurs="0" name="hybridFlowResponseType" nillable="true" type="xs:string"/>
<xs:element minOccurs="0" name="idTokenEncryptionAlgorithm" nillable="true" type="xs:string"/>
<xs:element minOccurs="0" name="idTokenEncryptionEnabled" type="xs:boolean"/>
<xs:element minOccurs="0" name="idTokenEncryptionMethod" nillable="true" type="xs:string"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,8 @@ OAuthConsumerAppDTO registerAndRetrieveOAuthApplicationData(OAuthConsumerAppDTO
app.setAudiences(application.getAudiences());
app.setPkceMandatory(application.getPkceMandatory());
app.setPkceSupportPlain(application.getPkceSupportPlain());
app.setHybridFlowEnabled(application.isHybridFlowEnabled());
app.setHybridFlowResponseType(application.getHybridFlowResponseType());
// Validate access token expiry configurations.
validateTokenExpiryConfigurations(application);
app.setUserAccessTokenExpiryTime(application.getUserAccessTokenExpiryTime());
Expand Down Expand Up @@ -796,6 +798,9 @@ void updateConsumerApplication(OAuthConsumerAppDTO consumerAppDTO, boolean enabl
oAuthAppDO.setApplicationName(consumerAppDTO.getApplicationName());
oAuthAppDO.setPkceMandatory(consumerAppDTO.getPkceMandatory());
oAuthAppDO.setPkceSupportPlain(consumerAppDTO.getPkceSupportPlain());
oAuthAppDO.setHybridFlowEnabled(consumerAppDTO.isHybridFlowEnabled());
oAuthAppDO.setHybridFlowResponseType(consumerAppDTO.getHybridFlowResponseType());

// Validate access token expiry configurations.
validateTokenExpiryConfigurations(consumerAppDTO);
oAuthAppDO.setUserAccessTokenExpiryTime(consumerAppDTO.getUserAccessTokenExpiryTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@ public static OAuthConsumerAppDTO buildConsumerAppDTO(OAuthAppDO appDO) {
dto.setState(appDO.getState());
dto.setPkceMandatory(appDO.isPkceMandatory());
dto.setPkceSupportPlain(appDO.isPkceSupportPlain());
dto.setHybridFlowEnabled(appDO.isHybridFlowEnabled());
dto.setHybridFlowResponseType(appDO.getHybridFlowResponseType());
dto.setUserAccessTokenExpiryTime(appDO.getUserAccessTokenExpiryTime());
dto.setApplicationAccessTokenExpiryTime(appDO.getApplicationAccessTokenExpiryTime());
dto.setRefreshTokenExpiryTime(appDO.getRefreshTokenExpiryTime());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.BACK_CHANNEL_LOGOUT_URL;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.BYPASS_CLIENT_CREDENTIALS;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.FRONT_CHANNEL_LOGOUT_URL;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.HYBRID_FLOW_ENABLED;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.HYBRID_FLOW_RESPONSE_TYPE;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ID_TOKEN_ENCRYPTED;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ID_TOKEN_ENCRYPTION_ALGORITHM;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.OIDCConfigProperties.ID_TOKEN_ENCRYPTION_METHOD;
Expand Down Expand Up @@ -1031,6 +1033,16 @@ private void addOrUpdateOIDCSpProperty(OAuthAppDO oauthAppDO,
SUBJECT_TOKEN_EXPIRY_TIME, String.valueOf(oauthAppDO.getSubjectTokenExpiryTime()),
prepStatementForPropertyAdd, preparedStatementForPropertyUpdate);

addOrUpdateOIDCSpProperty(preprocessedClientId, spTenantId, spOIDCProperties,
HYBRID_FLOW_ENABLED, String.valueOf(oauthAppDO.isHybridFlowEnabled()),
prepStatementForPropertyAdd, preparedStatementForPropertyUpdate);

if (oauthAppDO.isHybridFlowEnabled()) {
addOrUpdateOIDCSpProperty(preprocessedClientId, spTenantId, spOIDCProperties,
HYBRID_FLOW_RESPONSE_TYPE, oauthAppDO.getHybridFlowResponseType(),
prepStatementForPropertyAdd, preparedStatementForPropertyUpdate);
}

// Execute batched add/update/delete.
prepStatementForPropertyAdd.executeBatch();
preparedStatementForPropertyUpdate.executeBatch();
Expand Down Expand Up @@ -1668,6 +1680,14 @@ private void addServiceProviderOIDCProperties(Connection connection,
addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty,
SUBJECT_TOKEN_EXPIRY_TIME, String.valueOf(consumerAppDO.getSubjectTokenExpiryTime()));

addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty,
HYBRID_FLOW_ENABLED,
String.valueOf(consumerAppDO.isHybridFlowEnabled()));

addToBatchForOIDCPropertyAdd(processedClientId, spTenantId, prepStmtAddOIDCProperty,
OAuthConstants.OIDCConfigProperties.HYBRID_FLOW_RESPONSE_TYPE,
String.valueOf(consumerAppDO.getHybridFlowResponseType()));

prepStmtAddOIDCProperty.executeBatch();
}
}
Expand Down Expand Up @@ -1833,6 +1853,15 @@ private void setSpOIDCProperties(Map<String, List<String>> spOIDCProperties, OAu
if (subjectTokenExpiryTime != null) {
oauthApp.setSubjectTokenExpiryTime(Integer.parseInt(subjectTokenExpiryTime));
}

boolean hybridFlowEnabled = Boolean.parseBoolean(getFirstPropertyValue(spOIDCProperties,
HYBRID_FLOW_ENABLED));
oauthApp.setHybridFlowEnabled(hybridFlowEnabled);

String hybridFlowResponseType = getFirstPropertyValue(spOIDCProperties,
OAuthConstants.OIDCConfigProperties.HYBRID_FLOW_RESPONSE_TYPE);

oauthApp.setHybridFlowResponseType(hybridFlowResponseType);
}

private String getFirstPropertyValue(Map<String, List<String>> propertyMap, String key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public class OAuthAppDO extends InboundConfigurationProtocol implements Serializ
private String[] scopeValidators;
private boolean pkceSupportPlain;
private boolean pkceMandatory;
private boolean hybridFlowEnabled;
private String hybridFlowResponseType;
private String state;
private long userAccessTokenExpiryTime;
private long applicationAccessTokenExpiryTime;
Expand Down Expand Up @@ -196,10 +198,26 @@ public boolean isPkceMandatory() {
return pkceMandatory;
}

public boolean isHybridFlowEnabled() {
return hybridFlowEnabled;
}

public void setHybridFlowEnabled(boolean hybridFlowEnabled) {
this.hybridFlowEnabled = hybridFlowEnabled;
}

public void setPkceMandatory(boolean pkceMandatory) {
this.pkceMandatory = pkceMandatory;
}

public String getHybridFlowResponseType() {
return hybridFlowResponseType;
}

public void setHybridFlowResponseType(String hybridFlowResponseType) {
this.hybridFlowResponseType = hybridFlowResponseType;
}

public void setState(String state) {
this.state = state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ public class OAuthConsumerAppDTO implements InboundProtocolConfigurationDTO {
private String[] scopeValidators = null;
private boolean pkceSupportPlain;
private boolean pkceMandatory;
private boolean hybridFlowEnabled;
private String hybridFlowResponseType;
private String state;
private long userAccessTokenExpiryTime;
private long applicationAccessTokenExpiryTime;
Expand Down Expand Up @@ -205,6 +207,22 @@ public void setPkceMandatory(boolean pkceMandatory) {
this.pkceMandatory = pkceMandatory;
}

public boolean isHybridFlowEnabled() {
return hybridFlowEnabled;
}

public void setHybridFlowEnabled(boolean hybridFlowEnabled) {
this.hybridFlowEnabled = hybridFlowEnabled;
}

public String getHybridFlowResponseType() {
return hybridFlowResponseType;
}

public void setHybridFlowResponseType(String hybridFlowResponseType) {
this.hybridFlowResponseType = hybridFlowResponseType;
}

public void setState(String state) {
this.state = state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@
import org.wso2.carbon.utils.DiagnosticLog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

Expand Down Expand Up @@ -149,6 +152,9 @@ public OAuth2ClientValidationResponseDTO validateClientInfo(HttpServletRequest r
}
throw new InvalidOAuthClientException("Oauth application is not in active state.");
}

validateHybridFlowRequest(request, appDO);

return validateCallBack(clientId, callbackURI, appDO);
} catch (InvalidOAuthClientException e) {
// There is no such Client ID being registered. So it is a request from an invalid client.
Expand Down Expand Up @@ -187,6 +193,46 @@ public OAuth2ClientValidationResponseDTO validateClientInfo(HttpServletRequest r
}
}

private void validateHybridFlowRequest(HttpServletRequest request, OAuthAppDO appDO)
throws InvalidOAuthClientException {

String responseType = request.getParameter(OAuthConstants.OAuth20Params.RESPONSE_TYPE);
boolean hybridFlowEnabled = appDO.isHybridFlowEnabled();

if (OAuth2Util.isHybridResponseType(responseType)) {
if (!hybridFlowEnabled) {
if (log.isDebugEnabled()) {
log.debug("Hybrid flow is not enabled for the application with client ID: "
+ appDO.getOauthConsumerKey());
}
throw new InvalidOAuthClientException("Hybrid flow is not enabled for the application.");
}

String configuredHybridFlowResponseType = appDO.getHybridFlowResponseType();
if (!isRequestedResponseTypeConfigured(responseType, configuredHybridFlowResponseType)) {
if (log.isDebugEnabled()) {
log.debug("Requested response type " + responseType + " is not configured for the hybrid flow " +
"for the application with client ID: " + appDO.getOauthConsumerKey());
}

throw new InvalidOAuthClientException("Requested response type " + responseType +
" is not configured for the hybrid flow for the application.");
}
}
}

private boolean isRequestedResponseTypeConfigured(String responseType, String configuredHybridFlowResponseType) {

Set<String> configuredResponseTypes = new HashSet<>(Arrays.asList(configuredHybridFlowResponseType.split(" ")));
String[] requestedResponseTypes = responseType.split(" ");
for (String requestedType : requestedResponseTypes) {
if (!configuredResponseTypes.contains(requestedType)) {
return false;
}
}
return true;
}

private OAuth2ClientValidationResponseDTO validateCallBack(String clientId, String callbackURI, OAuthAppDO appDO) {

if (!parametersToValidate.contains(REDIRECT_URI)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,58 @@ public void testValidateClientInfoWithEmptyGrantTypes() throws Exception {
}
}

@Test(dataProvider = "ValidateClientInfoDataProvider")
public void testValidateHybridFlowValidRequest(String clientId, String grantType,
String callbackUrl, String tenantDomain,
int tenantId, String callbackURI) throws Exception {

OAuthAppDO oAuthAppDO = getOAuthAppDO(clientId, grantType, callbackUrl, tenantDomain, tenantId);
oAuthAppDO.setHybridFlowEnabled(true);
oAuthAppDO.setHybridFlowResponseType("code token");
when(mockHttpServletRequest.getParameter(CLIENT_ID)).thenReturn(clientId);
when(mockHttpServletRequest.getParameter(REDIRECT_URI)).thenReturn(callbackURI);
when(mockHttpServletRequest.getParameter(RESPONSE_TYPE)).thenReturn("code token");
OAuth2ClientValidationResponseDTO oAuth2ClientValidationResponseDTO = oAuth2Service.
validateClientInfo(mockHttpServletRequest);
assertNotNull(oAuth2ClientValidationResponseDTO);
assertTrue(oAuth2ClientValidationResponseDTO.isValidClient());
}

@Test(dataProvider = "ValidateClientInfoDataProvider")
public void testValidateHybridFlowDisabledCase(String clientId, String grantType,
String callbackUrl, String tenantDomain,
int tenantId, String callbackURI) throws Exception {

OAuthAppDO oAuthAppDO = getOAuthAppDO(clientId, grantType, callbackUrl, tenantDomain, tenantId);
oAuthAppDO.setHybridFlowEnabled(false);
when(mockHttpServletRequest.getParameter(CLIENT_ID)).thenReturn(clientId);
when(mockHttpServletRequest.getParameter(REDIRECT_URI)).thenReturn(callbackURI);
when(mockHttpServletRequest.getParameter(RESPONSE_TYPE)).thenReturn("code id_token");
OAuth2ClientValidationResponseDTO oAuth2ClientValidationResponseDTO = oAuth2Service.
validateClientInfo(mockHttpServletRequest);
assertNotNull(oAuth2ClientValidationResponseDTO);
assertEquals(oAuth2ClientValidationResponseDTO.getErrorCode(), "invalid_client");
assertFalse(oAuth2ClientValidationResponseDTO.isValidClient());
}

@Test(dataProvider = "ValidateClientInfoDataProvider")
public void testValidateHybridFlowInValidResponseType(String clientId, String grantType,
String callbackUrl, String tenantDomain,
int tenantId, String callbackURI) throws Exception {

OAuthAppDO oAuthAppDO = getOAuthAppDO(clientId, grantType, callbackUrl, tenantDomain, tenantId);
oAuthAppDO.setHybridFlowEnabled(true);
oAuthAppDO.setHybridFlowResponseType("code token");
when(mockHttpServletRequest.getParameter(CLIENT_ID)).thenReturn(clientId);
when(mockHttpServletRequest.getParameter(REDIRECT_URI)).thenReturn(callbackURI);
when(mockHttpServletRequest.getParameter(RESPONSE_TYPE)).thenReturn("code id_token");
OAuth2ClientValidationResponseDTO oAuth2ClientValidationResponseDTO = oAuth2Service.
validateClientInfo(mockHttpServletRequest);
assertNotNull(oAuth2ClientValidationResponseDTO);
assertEquals(oAuth2ClientValidationResponseDTO.getErrorCode(), "invalid_client");
assertFalse(oAuth2ClientValidationResponseDTO.isValidClient());
}

private OAuthAppDO getOAuthAppDO(String clientId, String grantType, String callbackUrl, String tenantDomain,
int tenantId, MockedStatic<IdentityTenantUtil> identityTenantUtil,
MockedStatic<OAuth2Util> oAuth2Util)
Expand Down

0 comments on commit 9a7c756

Please sign in to comment.