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

Introduce authenticated user validation in the refresh grant flow #2581

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
13 changes: 12 additions & 1 deletion components/org.wso2.carbon.identity.oauth/pom.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (c) 2015-2023, WSO2 LLC. (http://www.wso2.com).
~ Copyright (c) 2015-2024, 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
Expand Down Expand Up @@ -115,6 +115,10 @@
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.event</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.event.handler.accountlock</groupId>
<artifactId>org.wso2.carbon.identity.handler.event.account.lock</artifactId>
</dependency>
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.core</artifactId>
Expand Down Expand Up @@ -435,6 +439,7 @@
org.wso2.carbon.identity.organization.management.role.management.service.models; version="${carbon.identity.organization.management.version.range}",
org.wso2.carbon.identity.organization.management.organization.user.sharing.util;version="${carbon.identity.organization.management.version.range}",
org.wso2.carbon.identity.organization.management.organization.user.sharing.models;version="${carbon.identity.organization.management.version.range}",
org.wso2.carbon.identity.handler.event.account.lock.service.*;version="${account.lock.service.imp.pkg.version.range}",

org.wso2.carbon.base; version="${carbon.base.imp.pkg.version.range}",
org.wso2.carbon.registry.api;version="${carbon.kernel.registry.imp.pkg.version.range}",
Expand Down Expand Up @@ -547,6 +552,12 @@
</configuration>
</execution>
</executions>
<configuration>
<excludes>
<exclude>org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponent.class</exclude>
<exclude>org/wso2/carbon/identity/oauth2/internal/OAuth2ServiceComponentHolder.class</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ public class OAuthServerConfiguration {
private boolean isTokenRenewalPerRequestEnabled = false;
private boolean isRefreshTokenRenewalEnabled = true;
private boolean isExtendRenewedTokenExpiryTimeEnabled = true;
private boolean isValidateAuthenticatedUserForRefreshGrantEnabled = false;
private boolean assertionsUserNameEnabled = false;
private boolean accessTokenPartitioningEnabled = false;
private boolean redirectToRequestedRedirectUriEnabled = true;
Expand Down Expand Up @@ -437,6 +438,9 @@ private void buildOAuthServerConfiguration() {
// read refresh token renewal config
parseRefreshTokenRenewalConfiguration(oauthElem);

// Read the authenticated user validation config for refresh grant.
parseRefreshTokenGrantValidationConfiguration(oauthElem);

// read token persistence processor config
parseTokenPersistenceProcessorConfig(oauthElem);

Expand Down Expand Up @@ -1049,6 +1053,16 @@ public boolean isExtendRenewedTokenExpiryTimeEnabled() {
return isExtendRenewedTokenExpiryTimeEnabled;
}

/**
* Check if the authenticated user validation is enabled for refresh token grant flow.
*
* @return Returns true if the config is enabled.
*/
public boolean isValidateAuthenticatedUserForRefreshGrantEnabled() {

return isValidateAuthenticatedUserForRefreshGrantEnabled;
}

public Map<String, OauthTokenIssuer> getOauthTokenIssuerMap() {
return oauthTokenIssuerMap;
}
Expand Down Expand Up @@ -2451,6 +2465,20 @@ private void parseRefreshTokenRenewalConfiguration(OMElement oauthConfigElem) {
}
}

private void parseRefreshTokenGrantValidationConfiguration(OMElement oauthConfigElem) {
DilshanSenarath marked this conversation as resolved.
Show resolved Hide resolved

OMElement validateAuthenticatedUserForRefreshGrantElem = oauthConfigElem.getFirstChildWithName(
getQNameWithIdentityNS(ConfigElements.VALIDATE_AUTHENTICATED_USER_FOR_REFRESH_GRANT));
if (validateAuthenticatedUserForRefreshGrantElem != null) {
isValidateAuthenticatedUserForRefreshGrantEnabled =
Boolean.parseBoolean(validateAuthenticatedUserForRefreshGrantElem.getText());
}
if (log.isDebugEnabled()) {
log.debug("ValidateAuthenticatedUserForRefreshGrant was set to : " +
isValidateAuthenticatedUserForRefreshGrantEnabled);
}
}

private void parseAccessTokenPartitioningConfig(OMElement oauthConfigElem) {

OMElement enableAccessTokenPartitioningElem =
Expand Down Expand Up @@ -4154,6 +4182,9 @@ private class ConfigElements {
private static final String ENABLE_CACHE = "EnableOAuthCache";
// Enable/Disable refresh token renewal on each refresh_token grant request
private static final String RENEW_REFRESH_TOKEN_FOR_REFRESH_GRANT = "RenewRefreshTokenForRefreshGrant";
// Enable/Disable Authenticated user validation on refresh_token grant request.
private static final String VALIDATE_AUTHENTICATED_USER_FOR_REFRESH_GRANT =
"ValidateAuthenticatedUserForRefreshGrant";
// Enable/Disable extend the lifetime of the new refresh token
private static final String EXTEND_RENEWED_REFRESH_TOKEN_EXPIRY_TIME = "ExtendRenewedRefreshTokenExpiryTime";
// TokenPersistenceProcessor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.event.handler.AbstractEventHandler;
import org.wso2.carbon.identity.event.services.IdentityEventService;
import org.wso2.carbon.identity.handler.event.account.lock.service.AccountLockService;
import org.wso2.carbon.identity.oauth.common.OAuthConstants;
import org.wso2.carbon.identity.oauth.common.token.bindings.TokenBinderInfo;
import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration;
Expand Down Expand Up @@ -1612,4 +1613,23 @@ protected void unregisterConfigurationManager(ConfigurationManager configuration
}
OAuth2ServiceComponentHolder.getInstance().setConfigurationManager(null);
}

@Reference(
name = "org.wso2.carbon.identity.handler.event.account.lock.service.AccountLockService",
service = AccountLockService.class,
cardinality = ReferenceCardinality.MANDATORY,
policy = ReferencePolicy.DYNAMIC,
unbind = "unsetAccountLockService"
)
protected void setAccountLockService(AccountLockService accountLockService) {

OAuth2ServiceComponentHolder.setAccountLockService(accountLockService);
log.debug("AccountLockService set in OAuth2ServiceComponent bundle.");
}

protected void unsetAccountLockService(AccountLockService accountLockService) {

OAuth2ServiceComponentHolder.setAccountLockService(null);
log.debug("AccountLockService unset in OAuth2ServiceComponent bundle.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.wso2.carbon.identity.core.SAMLSSOServiceProviderManager;
import org.wso2.carbon.identity.core.handler.HandlerComparator;
import org.wso2.carbon.identity.event.services.IdentityEventService;
import org.wso2.carbon.identity.handler.event.account.lock.service.AccountLockService;
import org.wso2.carbon.identity.oauth.OAuthAdminServiceImpl;
import org.wso2.carbon.identity.oauth.dto.ScopeDTO;
import org.wso2.carbon.identity.oauth.tokenprocessor.DefaultOAuth2RevocationProcessor;
Expand Down Expand Up @@ -122,6 +123,7 @@ public class OAuth2ServiceComponentHolder {

private List<ImpersonationValidator> impersonationValidators = new ArrayList<>();
private ConfigurationManager configurationManager;
private static AccountLockService accountLockService;


private OAuth2ServiceComponentHolder() {
Expand Down Expand Up @@ -889,4 +891,24 @@ public void setConfigurationManager(ConfigurationManager configurationManager) {

this.configurationManager = configurationManager;
}

/**
* Set the account lock service to the OAuth2ServiceComponentHolder.
*
* @param accountLockService Account lock service instance.
*/
public static void setAccountLockService(AccountLockService accountLockService) {

OAuth2ServiceComponentHolder.accountLockService = accountLockService;
}

/**
* Retrieve the account lock service.
*
* @return Account lock service instance.
*/
public static AccountLockService getAccountLockService() {

return OAuth2ServiceComponentHolder.accountLockService;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,15 @@
import org.wso2.carbon.identity.action.execution.exception.ActionExecutionException;
import org.wso2.carbon.identity.action.execution.model.ActionExecutionStatus;
import org.wso2.carbon.identity.action.execution.model.ActionType;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.exception.UserIdNotFoundException;
import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.base.IdentityConstants;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.identity.handler.event.account.lock.exception.AccountLockServiceException;
import org.wso2.carbon.identity.handler.event.account.lock.service.AccountLockService;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCache;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheEntry;
import org.wso2.carbon.identity.oauth.cache.AuthorizationGrantCacheKey;
Expand All @@ -52,11 +57,16 @@
import org.wso2.carbon.identity.oauth2.internal.OAuth2ServiceComponentHolder;
import org.wso2.carbon.identity.oauth2.model.AccessTokenDO;
import org.wso2.carbon.identity.oauth2.model.RefreshTokenValidationDataDO;
import org.wso2.carbon.identity.oauth2.token.AccessTokenIssuer;
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext;
import org.wso2.carbon.identity.oauth2.token.OauthTokenIssuer;
import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinder;
import org.wso2.carbon.identity.oauth2.token.bindings.TokenBinding;
import org.wso2.carbon.identity.oauth2.util.OAuth2Util;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.FederatedAssociationManager;
import org.wso2.carbon.identity.user.profile.mgt.association.federation.exception.FederatedAssociationManagerException;
import org.wso2.carbon.user.core.UserCoreConstants;
import org.wso2.carbon.user.core.util.UserCoreUtil;

import java.sql.Timestamp;
import java.util.Arrays;
Expand All @@ -69,6 +79,8 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkErrorConstants.ErrorMessages.ERROR_WHILE_CHECKING_ACCOUNT_LOCK_STATUS;
import static org.wso2.carbon.identity.application.authentication.framework.util.FrameworkErrorConstants.ErrorMessages.ERROR_WHILE_GETTING_USERNAME_ASSOCIATED_WITH_IDP;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.GrantTypes.REFRESH_TOKEN;
import static org.wso2.carbon.identity.oauth.common.OAuthConstants.TokenBindings.NONE;
import static org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration.JWT_TOKEN_TYPE;
Expand All @@ -86,6 +98,8 @@ public class RefreshGrantHandler extends AbstractAuthorizationGrantHandler {
public static final String DEACTIVATED_ACCESS_TOKEN = "DeactivatedAccessToken";
private static final Log log = LogFactory.getLog(RefreshGrantHandler.class);
private boolean isHashDisabled = OAuth2Util.isHashDisabled();
private static final String ACCOUNT_LOCK_ERROR_MESSAGE = "Account is locked for user %s in tenant %s. Cannot" +
" login until the account is unlocked.";

@Override
public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx)
Expand All @@ -98,6 +112,7 @@ public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx)

validateRefreshTokenInRequest(tokenReq, validationBean);
validateTokenBindingReference(tokenReq, validationBean);
validateAuthenticatedUser(validationBean, tokReqMsgCtx);

if (log.isDebugEnabled()) {
log.debug("Refresh token validation successful for Client id : " + tokenReq.getClientId() +
Expand Down Expand Up @@ -282,6 +297,63 @@ private boolean validateRefreshTokenStatus(RefreshTokenValidationDataDO validati
return true;
}

private boolean validateAuthenticatedUser(RefreshTokenValidationDataDO validationBean,
DilshanSenarath marked this conversation as resolved.
Show resolved Hide resolved
DilshanSenarath marked this conversation as resolved.
Show resolved Hide resolved
OAuthTokenReqMessageContext oAuthTokenReqMessageContext)
throws IdentityOAuth2Exception {

if (!OAuthServerConfiguration.getInstance().isValidateAuthenticatedUserForRefreshGrantEnabled()) {
return true;
}

AuthenticatedUser authenticatedUser = validationBean.getAuthorizedUser();
if (authenticatedUser != null) {
String username = null;
String tenantDomain = null;

if (authenticatedUser.isFederatedUser()) {
try {
FederatedAssociationManager federatedAssociationManager =
FrameworkUtils.getFederatedAssociationManager();
OAuthAppDO oAuthAppDO =
(OAuthAppDO) oAuthTokenReqMessageContext.getProperty(AccessTokenIssuer.OAUTH_APP_DO);
String oAuthAppTenantDomain = OAuth2Util.getTenantDomainOfOauthApp(oAuthAppDO);
String associatedLocalUsername =
federatedAssociationManager.getUserForFederatedAssociation(oAuthAppTenantDomain,
authenticatedUser.getFederatedIdPName(),
authenticatedUser.getAuthenticatedSubjectIdentifier());
if (associatedLocalUsername != null) {
username = associatedLocalUsername;
tenantDomain = oAuthAppTenantDomain;
}
} catch (FederatedAssociationManagerException | FrameworkException e) {
throw new IdentityOAuth2Exception(ERROR_WHILE_GETTING_USERNAME_ASSOCIATED_WITH_IDP.getCode(),
String.format(ERROR_WHILE_GETTING_USERNAME_ASSOCIATED_WITH_IDP.getMessage(),
authenticatedUser.getFederatedIdPName()), e);
}
} else {
username = UserCoreUtil.addDomainToName(authenticatedUser.getUserName(),
authenticatedUser.getUserStoreDomain());
tenantDomain = authenticatedUser.getTenantDomain();
}

if (username != null && tenantDomain != null) {
AccountLockService accountLockService = OAuth2ServiceComponentHolder.getAccountLockService();

try {
boolean accountLockStatus = accountLockService.isAccountLocked(username, tenantDomain);
if (accountLockStatus) {
throw new IdentityOAuth2Exception(UserCoreConstants.ErrorCode.USER_IS_LOCKED,
String.format(ACCOUNT_LOCK_ERROR_MESSAGE, username, tenantDomain));
}
} catch (AccountLockServiceException e) {
throw new IdentityOAuth2Exception(ERROR_WHILE_CHECKING_ACCOUNT_LOCK_STATUS.getCode(),
String.format(ERROR_WHILE_CHECKING_ACCOUNT_LOCK_STATUS.getMessage(), username), e);
}
}
}

return true;
}

private OAuth2AccessTokenRespDTO buildTokenResponse(OAuthTokenReqMessageContext tokReqMsgCtx,
AccessTokenDO accessTokenBean) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
* Copyright (c) 2017-2024, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except
Expand Down Expand Up @@ -505,6 +505,13 @@ public void testGetSupportedTokenEndpointSigningAlgorithms() {
Assert.assertEquals(supportedTokenEndpointSigningAlgorithms.size(), 3);
}

@Test
public void testIsValidateAuthenticatedUserForRefreshGrantEnabled() throws Exception {

Assert.assertTrue(OAuthServerConfiguration.getInstance()
.isValidateAuthenticatedUserForRefreshGrantEnabled());
}

private String fillURLPlaceholdersForTest(String url) {

return url.replace("${carbon.protocol}", "https")
Expand Down
Loading
Loading