Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improve the code to include hybrid flow response type #2461

Merged
merged 2 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method will be executed when creating app, don't we need configure default value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When creating the application hybrid flow is disabled, Therefore we don't need to assign a default value.

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 {
asha15 marked this conversation as resolved.
Show resolved Hide resolved

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(" ")));
asha15 marked this conversation as resolved.
Show resolved Hide resolved
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,27 @@ 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 {

try (MockedStatic<OAuth2Util> oAuth2Util = mockStatic(OAuth2Util.class);
MockedStatic<IdentityTenantUtil> identityTenantUtil = mockStatic(IdentityTenantUtil.class)) {
OAuthAppDO oAuthAppDO = getOAuthAppDO(clientId, grantType, callbackUrl, tenantDomain,
tenantId, identityTenantUtil, oAuth2Util);
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());
}
}

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