Skip to content
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 @@ -24,7 +24,9 @@
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.context.SessionContext;
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.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.common.model.ServiceProviderProperty;
Expand Down Expand Up @@ -675,6 +677,17 @@ private OAuth2IntrospectionResponseDTO validateAccessToken(OAuth2TokenValidation
StringUtils.isNotBlank(accessTokenDO.getTokenBinding().getBindingValue())) {
introResp.setCnfBindingValue(accessTokenDO.getTokenBinding().getBindingValue());
}

// Validate SSO session bound token.
if (OAuth2Constants.TokenBinderType.SSO_SESSION_BASED_TOKEN_BINDER.equals(bindingType)) {
if (!isTokenBoundToActiveSSOSession(accessTokenDO)) {
if (log.isDebugEnabled()) {
log.debug("Token is not bound to an active SSO session.");
}
introResp.setActive(false);
return introResp;
}
}
}
// add authorized user type
if (tokenType != null) {
Expand Down Expand Up @@ -1037,4 +1050,36 @@ private void validateIntrospectionForSubOrgTokens(String tenantDomain, AccessTok
}
}
}

/**
* Check whether the SSO-session-bound access token is still tied to an active SSO session.
*
* @param accessTokenDO the access token data object to validate.
* @return {@code true} if the token is bound to an active SSO session, {@code false} otherwise.
*/
private boolean isTokenBoundToActiveSSOSession(AccessTokenDO accessTokenDO) {

if (StringUtils.isBlank(accessTokenDO.getTokenBinding().getBindingValue())) {
if (log.isDebugEnabled()) {
log.debug("No token binding value is found for SSO session bound token.");
}
return false;
}

String sessionIdentifier = accessTokenDO.getTokenBinding().getBindingValue();
String tenantDomain = accessTokenDO.getAuthzUser().getTenantDomain();
SessionContext sessionContext = FrameworkUtils.getSessionContextFromCache(sessionIdentifier, tenantDomain);
if (sessionContext == null) {
if (log.isDebugEnabled()) {
log.debug("Session context is not found corresponding to the session identifier: " +
sessionIdentifier);
}
return false;
}

if (log.isDebugEnabled()) {
log.debug("SSO session validation successful for the given session identifier: " + sessionIdentifier);
}
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
import org.testng.annotations.Test;
import org.wso2.carbon.base.MultitenantConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.authentication.framework.context.SessionContext;
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.application.common.model.FederatedAuthenticatorConfig;
import org.wso2.carbon.identity.application.common.model.IdentityProvider;
import org.wso2.carbon.identity.application.common.model.Property;
Expand Down Expand Up @@ -82,6 +84,7 @@
import java.sql.Connection;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -124,6 +127,7 @@ public class TokenValidationHandlerTest {
private Connection conn = null;
private TokenValidationHandler tokenValidationHandler;
private OAuth2JWTTokenValidator oAuth2JWTTokenValidator;
private static final String SSO_SESSION_BINDING_REFERENCE = "sso_session_binding_ref";

@Mock
private OAuth2TokenValidator tokenValidator;
Expand Down Expand Up @@ -153,6 +157,7 @@ public void setUp() {

authzUser = new AuthenticatedUser();
authzUser.setAccessingOrganization("test_org");
authzUser.setTenantDomain("carbon.super");
issuedTime = new Timestamp(System.currentTimeMillis());
refreshTokenIssuedTime = new Timestamp(System.currentTimeMillis());
validityPeriodInMillis = 3600000L;
Expand Down Expand Up @@ -434,7 +439,8 @@ public void testValidateOrgSwitchedJWTToken(String clientAppTenantDomain,
MockedStatic<OAuth2ServiceComponentHolder> oAuth2ServiceComponentHolder =
mockStatic(OAuth2ServiceComponentHolder.class);
MockedStatic<OrganizationManagementConfigUtil> organizationManagementConfigUtil =
mockStatic(OrganizationManagementConfigUtil.class);) {
mockStatic(OrganizationManagementConfigUtil.class);
MockedStatic<FrameworkUtils> frameworkUtils = mockStatic(FrameworkUtils.class)) {
mockRequiredObjects(oAuthServerConfiguration, identityDatabaseUtil);
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(clientAppTenantDomain);
PrivilegedCarbonContext.getThreadLocalCarbonContext().setOrganizationId(resourceResidentOrganizationId);
Expand Down Expand Up @@ -610,4 +616,109 @@ public void testBuildClientAppDTOWithInvalidAccessToken() throws Exception {
assertFalse(result.getAccessTokenValidationResponse().isValid(), "Expected the token to be invalid");
}
}

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

return new Object[][]{
{true, true},
{false, false}
};
}

@Test(dataProvider = "ssoSessionBoundTokenStatusDataProvider")
public void testBuildIntrospectionResponseForSSOSessionBoundAccessToken(boolean isSessionValid,
boolean expectedActiveStatus)
throws Exception {

try (MockedStatic<OAuthServerConfiguration> oAuthServerConfiguration = mockStatic(
OAuthServerConfiguration.class);
MockedStatic<IdentityDatabaseUtil> identityDatabaseUtil = mockStatic(IdentityDatabaseUtil.class);
MockedStatic<OAuth2ServiceComponentHolder> oAuth2ServiceComponentHolder =
mockStatic(OAuth2ServiceComponentHolder.class);
MockedStatic<OAuth2Util> oAuth2Util = mockStatic(OAuth2Util.class);
MockedStatic<IdentityUtil> identityUtil = mockStatic(IdentityUtil.class);
MockedStatic<OrganizationManagementUtil> organizationManagementUtil =
mockStatic(OrganizationManagementUtil.class);
MockedStatic<FrameworkUtils> frameworkUtils = mockStatic(FrameworkUtils.class)) {

organizationManagementUtil.when(() -> OrganizationManagementUtil.isOrganization(anyString()))
.thenReturn(false);
OAuth2ServiceComponentHolder.setIDPIdColumnEnabled(true);
mockRequiredObjects(oAuthServerConfiguration, identityDatabaseUtil);

oAuthServerConfiguration.when(
() -> OAuthServerConfiguration.getInstance().isCrossTenantTokenIntrospectionAllowed())
.thenReturn(true);
oAuthServerConfiguration.when(
() -> OAuthServerConfiguration.getInstance().allowCrossTenantIntrospectionForSubOrgTokens())
.thenReturn(true);
oAuthServerConfiguration.when(() -> OAuthServerConfiguration.getInstance().getAllowedScopes())
.thenReturn(Collections.emptyList());

OAuth2ServiceComponentHolder oAuth2ServiceComponentHolderInstance =
Mockito.mock(OAuth2ServiceComponentHolder.class);
oAuth2ServiceComponentHolder.when(OAuth2ServiceComponentHolder::getInstance)
.thenReturn(oAuth2ServiceComponentHolderInstance);

OAuthComponentServiceHolder.getInstance().setRealmService(realmService);
IdentityTenantUtil.setRealmService(realmService);
lenient().when(realmService.getBootstrapRealmConfiguration()).thenReturn(realmConfiguration);
identityUtil.when(IdentityUtil::getPrimaryDomainName).thenReturn("PRIMARY");

PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain("carbon.super");
PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(MultitenantConstants.SUPER_TENANT_ID);

OAuth2TokenValidationRequestDTO validationRequest = new OAuth2TokenValidationRequestDTO();
OAuth2TokenValidationRequestDTO.OAuth2AccessToken accessToken = validationRequest.new OAuth2AccessToken();
accessToken.setIdentifier("sso-session-access-token");
accessToken.setTokenType("bearer");
validationRequest.setAccessToken(accessToken);

AccessTokenDO accessTokenDO = new AccessTokenDO(clientId, authzUser, scopeArraySorted, issuedTime,
refreshTokenIssuedTime, validityPeriodInMillis, refreshTokenValidityPeriodInMillis, tokenType,
authorizationCode);
accessTokenDO.setTokenId("sso-session-token-id");

TokenBinding tokenBinding = new TokenBinding();
tokenBinding.setBindingType(OAuth2Constants.TokenBinderType.SSO_SESSION_BASED_TOKEN_BINDER);
tokenBinding.setBindingReference(SSO_SESSION_BINDING_REFERENCE);
tokenBinding.setBindingValue("sso_session_binding_value");
accessTokenDO.setTokenBinding(tokenBinding);

TokenProvider tokenProvider = Mockito.mock(TokenProvider.class);
when(oAuth2ServiceComponentHolderInstance.getTokenProvider()).thenReturn(tokenProvider);
when(tokenProvider.getVerifiedAccessToken(anyString(), anyBoolean())).thenReturn(accessTokenDO);
oAuth2Util.when(() -> OAuth2Util.getAppInformationByAccessTokenDO(any())).thenReturn(new OAuthAppDO());

ServiceProvider serviceProvider = new ServiceProvider();
serviceProvider.setApplicationVersion("v1.0.0");
oAuth2Util.when(() -> OAuth2Util.getServiceProvider(anyString(), any()))
.thenReturn(serviceProvider);
oAuth2Util.when(() -> OAuth2Util.getAccessTokenExpireMillis(any(), anyBoolean())).thenReturn(1000L);
// As the token is dummy, no point in getting actual tenant details.
oAuth2Util.when(() -> OAuth2Util.getTenantDomain(anyInt())).thenReturn(StringUtils.EMPTY);

SessionContext sessionContext = isSessionValid ? new SessionContext() : null;
frameworkUtils.when(() -> FrameworkUtils.getSessionContextFromCache(anyString(), anyString()))
.thenReturn(sessionContext);

OAuth2IntrospectionResponseDTO introspectionResponse = tokenValidationHandler
.buildIntrospectionResponse(validationRequest);

assertNotNull(introspectionResponse, "Introspection response should not be null");
if (expectedActiveStatus) {
assertTrue(introspectionResponse.isActive(),
"Token should be active when SSO session is valid");
assertEquals(introspectionResponse.getBindingType(),
OAuth2Constants.TokenBinderType.SSO_SESSION_BASED_TOKEN_BINDER,
"Binding type should match SSO session binder");
assertEquals(introspectionResponse.getBindingReference(), SSO_SESSION_BINDING_REFERENCE,
"Binding reference should be preserved");
} else {
assertFalse(introspectionResponse.isActive(),
"Token should be inactive when SSO session has timed out");
}
}
}
}