Skip to content

Commit

Permalink
fix hybrid flow
Browse files Browse the repository at this point in the history
# Conflicts:
#	components/org.wso2.carbon.identity.oauth/src/main/java/org/wso2/carbon/identity/oauth2/authz/validators/AbstractResponseTypeRequestValidator.java
  • Loading branch information
Thumimku committed Jan 23, 2025
1 parent c383ee3 commit c1392c1
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ public class OAuthServerConfiguration {
private List<String> supportedTokenEndpointSigningAlgorithms = new ArrayList<>();
private Boolean roleBasedScopeIssuerEnabledConfig = false;
private String scopeMetadataExtensionImpl = null;
private static final List<String> HYBRID_RESPONSE_TYPES = Arrays.asList("code token",
"code id_token", "code id_token token");
private List<String> configuredHybridResponseTypes = new ArrayList<>();

private final List<String> restrictedQueryParameters = new ArrayList<>();

Expand Down Expand Up @@ -975,6 +978,11 @@ public boolean useRetainOldAccessTokens() {
return Boolean.TRUE.toString().equalsIgnoreCase(retainOldAccessTokens);
}

public List<String> getConfiguredHybridResponseTypes() {

return configuredHybridResponseTypes;
}

public boolean isTokenCleanupEnabled() {

return Boolean.TRUE.toString().equalsIgnoreCase(tokenCleanupFeatureEnable);
Expand Down Expand Up @@ -2978,6 +2986,11 @@ private void parseSupportedResponseTypesConfig(OMElement oauthConfigElem) {
}
if (responseTypeName != null && !"".equals(responseTypeName) &&
responseTypeHandlerImplClass != null && !"".equals(responseTypeHandlerImplClass)) {

// check for the configured hybrid response type
if (HYBRID_RESPONSE_TYPES.contains(responseTypeName)) {
configuredHybridResponseTypes.add(responseTypeName);
}
supportedResponseTypeClassNames.put(responseTypeName, responseTypeHandlerImplClass);
OMElement responseTypeValidatorClassNameElement = supportedResponseTypeElement
.getFirstChildWithName(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1992,14 +1992,41 @@ private void setSpOIDCProperties(Map<String, List<String>> spOIDCProperties, OAu
oauthApp.setSubjectTokenExpiryTime(Integer.parseInt(subjectTokenExpiryTime));
}

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

String hybridFlowResponseType = getFirstPropertyValue(spOIDCProperties,
OAuthConstants.OIDCConfigProperties.HYBRID_FLOW_RESPONSE_TYPE);
// Check if the application has the `hybridFlowEnabled` property configured
if (hybridFlowEnabledProperty == null) {
// No hybridFlowEnabled property; use server's default behavior for hybrid flow.
if (LOG.isDebugEnabled()) {
LOG.debug(String.format("The application with consumer key %s does not have the 'hybridFlowEnabled' " +
"property configured. Using server default behavior to enable hybrid flow with all " +
"configured response types.", oauthApp.getOauthConsumerKey()));
}

oauthApp.setHybridFlowResponseType(hybridFlowResponseType);
List<String> configuredHybridResponseTypes = OAuthServerConfiguration.getInstance()
.getConfiguredHybridResponseTypes();

if (configuredHybridResponseTypes.isEmpty()) {
// No configured hybrid response types; hybrid flow is disabled
oauthApp.setHybridFlowEnabled(false);
oauthApp.setHybridFlowResponseType(null);
} else {
// Enable hybrid flow with all configured response types
oauthApp.setHybridFlowEnabled(true);
String hybridFlowResponseType = String.join(", ", configuredHybridResponseTypes);
oauthApp.setHybridFlowResponseType(hybridFlowResponseType);
}
} else {
// Hybrid flow property is defined; parse and configure
boolean hybridFlowEnabled = Boolean.parseBoolean(hybridFlowEnabledProperty);
oauthApp.setHybridFlowEnabled(hybridFlowEnabled);

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

// Configure the hybrid flow response type (null if not explicitly set)
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 @@ -208,6 +208,7 @@ private void validateHybridFlowRequest(HttpServletRequest request, OAuthAppDO ap
String responseType = request.getParameter(OAuthConstants.OAuth20Params.RESPONSE_TYPE);
boolean hybridFlowEnabled = appDO.isHybridFlowEnabled();

// Check if the response type is a hybrid response type.
if (OAuth2Util.isHybridResponseType(responseType)) {
if (!hybridFlowEnabled) {
if (log.isDebugEnabled()) {
Expand All @@ -217,30 +218,35 @@ private void validateHybridFlowRequest(HttpServletRequest request, OAuthAppDO ap
throw new InvalidOAuthClientException("Hybrid flow is not enabled for the application.");
}

String configuredHybridFlowResponseType = appDO.getHybridFlowResponseType();
if (!isRequestedResponseTypeConfigured(responseType, configuredHybridFlowResponseType)) {
// Retrieve the list of allowed hybrid response types
List<String> hybridResponseTypeList = getHybridResponseType(appDO);

// Validate the requested response type
if (!hybridResponseTypeList.contains(responseType)) {
String message = OAuthConstants.OAuthError.AuthorizationResponsei18nKey
.INVALID_RESPONSE_TYPE_FOR_HYBRID_FLOW;
if (log.isDebugEnabled()) {
log.debug("Requested response type " + responseType + " is not configured for the hybrid flow " +
"for the application with client ID: " + appDO.getOauthConsumerKey());
log.debug(String.format("Requested response type '%s' is not configured for the hybrid " +
"flow for the application with client ID: %s", responseType, appDO.getOauthConsumerKey()));
}

throw new InvalidOAuthClientException(message);
}
}
}

private boolean isRequestedResponseTypeConfigured(String responseType, String configuredHybridFlowResponseType) {
private List<String> getHybridResponseType(OAuthAppDO appDO) throws InvalidOAuthClientException {

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

// Validate if the configured response type string is null or empty
if (configuredHybridFlowResponseType == null || configuredHybridFlowResponseType.trim().isEmpty()) {
throw new InvalidOAuthClientException(String.format("No hybrid flow response types are configured " +
"for the application with client ID: %s.", appDO.getOauthConsumerKey()));
}
return true;

// Split the configured hybrid response types into a list
return Arrays.asList(configuredHybridFlowResponseType.split(","));
}

private OAuth2ClientValidationResponseDTO validateCallBack(String clientId, String callbackURI, OAuthAppDO appDO) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;

Expand Down Expand Up @@ -1164,6 +1165,115 @@ public void testGetAppInformationByAppNameWithAppResidentOrgIdAndWithException()
}
}

@DataProvider(name = "testGetAppInformationForHybridFlowData")
public Object[][] testGetAppInformationForHybridFlowData() {

return new Object[][]{
{true, "code token", true, "code token"},
{true, "code token, code id_token", true, "code token, code id_token"},
{false, "code id_token", false, "code id_token"},
};
}

@Test(dataProvider = "testGetAppInformationForHybridFlowData")
public void testGetAppInformationForHybridFlowTest
(boolean hybridFlowEnabled, String hybridFlowResponseType,
boolean hybridFlowEnabledExpected, String hybridFlowResponseTypeExpected) throws Exception {

try (MockedStatic<OAuthServerConfiguration> oAuthServerConfiguration = mockStatic(
OAuthServerConfiguration.class);
MockedStatic<IdentityTenantUtil> identityTenantUtil = mockStatic(IdentityTenantUtil.class);
MockedStatic<IdentityUtil> identityUtil = mockStatic(IdentityUtil.class);
MockedStatic<IdentityDatabaseUtil> identityDatabaseUtil = mockStatic(IdentityDatabaseUtil.class);
MockedStatic<OAuthComponentServiceHolder> oAuthComponentServiceHolder =
mockStatic(OAuthComponentServiceHolder.class)) {
setupMocksForTest(oAuthServerConfiguration, identityTenantUtil, identityUtil);
mockUserstore(oAuthComponentServiceHolder);
try (Connection connection = getConnection(DB_NAME)) {
mockIdentityUtilDataBaseConnection(connection, identityDatabaseUtil);
OAuthAppDO defaultOAuthAppDO = getDefaultOAuthAppDO();

// Add Impersonation OIDC properties.
defaultOAuthAppDO.setHybridFlowEnabled(hybridFlowEnabled);
defaultOAuthAppDO.setHybridFlowResponseType(hybridFlowResponseType);
addOAuthApplication(defaultOAuthAppDO, TENANT_ID);

OAuthAppDAO appDAO = new OAuthAppDAO();
OAuthAppDO oAuthAppDO = appDAO.getAppInformation(CONSUMER_KEY);
assertNotNull(oAuthAppDO);
assertEquals(oAuthAppDO.isHybridFlowEnabled(), hybridFlowEnabledExpected);
assertEquals(oAuthAppDO.getHybridFlowResponseType(), hybridFlowResponseTypeExpected);
appDAO.removeConsumerApplication(CONSUMER_KEY);
}
} finally {
resetPrivilegedCarbonContext();
}
}

@DataProvider(name = "testGetMigratedAppInformationForHybridFlowData")
public Object[][] testGetMigratedAppInformationForHybridFlowData() {

return new Object[][]{
{Arrays.asList("code token", "code id_token", "code id_token token"), true,
"code token, code id_token, code id_token token"},
{Arrays.asList("code id_token"), true, "code id_token"},
{Arrays.asList(), false, null},
};
}

@Test(dataProvider = "testGetMigratedAppInformationForHybridFlowData")
public void testGetMigratedAppInformationForHybridFlowTest(List<String> configuredHybridFlowResponseType,
boolean hybridFlowEnabledExpected,
String hybridFlowResponseTypeExpected) throws Exception {

try (MockedStatic<OAuthServerConfiguration> oAuthServerConfiguration = mockStatic(
OAuthServerConfiguration.class);
MockedStatic<IdentityTenantUtil> identityTenantUtil = mockStatic(IdentityTenantUtil.class);
MockedStatic<IdentityUtil> identityUtil = mockStatic(IdentityUtil.class);
MockedStatic<IdentityDatabaseUtil> identityDatabaseUtil = mockStatic(IdentityDatabaseUtil.class);
MockedStatic<OAuthComponentServiceHolder> oAuthComponentServiceHolder =
mockStatic(OAuthComponentServiceHolder.class)) {
mockUserstore(oAuthComponentServiceHolder);
final String deleteHybridFlowProperty =
createDeleteQuery(CONSUMER_KEY, "hybridFlowEnabled");
final String deleteHybridFlowResponseTypeProperty =
createDeleteQuery(CONSUMER_KEY, "hybridFlowResponseType");
setupMocksForTest(oAuthServerConfiguration, identityTenantUtil, identityUtil);
when(mockedServerConfig.getConfiguredHybridResponseTypes()).thenReturn(configuredHybridFlowResponseType);
try (Connection connection = getConnection(DB_NAME)) {
mockIdentityUtilDataBaseConnection(connection, identityDatabaseUtil);
OAuthAppDO defaultOAuthAppDO = getDefaultOAuthAppDO();

addOAuthApplication(defaultOAuthAppDO, TENANT_ID);

executeDeleteQuery(connection, deleteHybridFlowProperty);
executeDeleteQuery(connection, deleteHybridFlowResponseTypeProperty);

OAuthAppDAO appDAO = new OAuthAppDAO();
OAuthAppDO oAuthAppDO = appDAO.getAppInformation(CONSUMER_KEY);
assertNotNull(oAuthAppDO);
assertEquals(oAuthAppDO.isHybridFlowEnabled(), hybridFlowEnabledExpected);
assertEquals(oAuthAppDO.getHybridFlowResponseType(), hybridFlowResponseTypeExpected);
appDAO.removeConsumerApplication(CONSUMER_KEY);
}
} finally {
resetPrivilegedCarbonContext();
}
}

private String createDeleteQuery(String consumerKey, String propertyKey) {

return "DELETE FROM IDN_OIDC_PROPERTY WHERE CONSUMER_KEY='"
+ consumerKey + "' AND PROPERTY_KEY ='" + propertyKey + "'";
}

private void executeDeleteQuery(Connection connection, String query) throws SQLException {

try (PreparedStatement stmt = connection.prepareStatement(query)) {
stmt.execute();
}
}

private OAuthAppDO getDefaultOAuthAppDO() {
AuthenticatedUser authenticatedUser = new AuthenticatedUser();
authenticatedUser.setUserName(USER_NAME);
Expand Down
Loading

0 comments on commit c1392c1

Please sign in to comment.