Skip to content
Draft
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 @@ -4,7 +4,7 @@
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
Expand Down Expand Up @@ -100,6 +100,7 @@ public AuthenticationStatus handleAuthenticate(CDI cdi, String realmName, @Sensi
}

public AuthenticationStatus validateWithIdentityStore(String realmName, Subject clientSubject, @Sensitive Credential credential, IdentityStoreHandler identityStoreHandler) {

AuthenticationStatus status = AuthenticationStatus.SEND_FAILURE;
CredentialValidationResult result = identityStoreHandler.validate(credential);
if (result.getStatus() == CredentialValidationResult.Status.VALID) {
Expand All @@ -111,6 +112,25 @@ public AuthenticationStatus validateWithIdentityStore(String realmName, Subject
return status;
}

/**
* Check if identity stores are skipped via webAppSecurity configuration.
*
* @return true if skipped
*/
private boolean isIdentityStoresSkipped() {
try {
com.ibm.ws.webcontainer.security.WebAppSecurityConfig config = com.ibm.ws.webcontainer.security.util.WebConfigUtils.getWebAppSecurityConfig();
if (config != null) {
return config.getSkipIdentityStores();
}
} catch (Exception e) {
if (tc.isDebugEnabled()) {
Tr.debug(tc, "Error checking skipIdentityStores config", e);
}
}
return false;
}

private AuthenticationStatus validateWithUserRegistry(Subject clientSubject, @Sensitive Credential credential,
CallbackHandler handler) throws AuthenticationException {
AuthenticationStatus status = AuthenticationStatus.SEND_FAILURE;
Expand Down Expand Up @@ -249,6 +269,14 @@ public IdentityStoreHandler getIdentityStoreHandler(CDI cdi) {

@SuppressWarnings({ "unchecked", "rawtypes" })
public boolean isIdentityStoreAvailable(CDI cdi) {
// check if identity stores are skipped via webAppSecurity config
if (isIdentityStoresSkipped()) {
if (tc.isDebugEnabled()) {
Tr.debug(tc, "Identity stores skipped via webAppSecurity configuration");
}
return false;
}

Instance<IdentityStore> identityStoreInstances = cdi.select(IdentityStore.class);
if (identityStoreInstances != null && !identityStoreInstances.isUnsatisfied() && !identityStoreInstances.isAmbiguous()) {
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*******************************************************************************
* Copyright (c) 2024 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package com.ibm.ws.security.javaeesec.cdi.beans;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

import javax.security.auth.Subject;
import javax.security.enterprise.AuthenticationStatus;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.IdentityStoreHandler;

import org.jmock.Expectations;
import org.jmock.Mockery;
import org.jmock.integration.junit4.JUnit4Mockery;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
* Unit tests for Utils.validateWithIdentityStore with skipIdentityStores functionality.
*/
public class UtilsSkipIdentityStoresTest {

private final Mockery mockery = new JUnit4Mockery();
private Utils utils;
private IdentityStoreHandler identityStoreHandler;
private Subject clientSubject;
private UsernamePasswordCredential credential;
private static final String REALM_NAME = "testRealm";

@Before
public void setUp() {
utils = new Utils();
identityStoreHandler = mockery.mock(IdentityStoreHandler.class);
clientSubject = new Subject();
credential = new UsernamePasswordCredential("testUser", "testPassword");
}

@After
public void tearDown() {
mockery.assertIsSatisfied();
}

/**
* Normal operation: skipIdentityStores is false and valid credentials,
* so return SUCCESS.
*/
@Test
public void testValidateWithIdentityStore_SkipIdentityStoresFalse_ValidCredentials() {
final CredentialValidationResult validResult = new CredentialValidationResult("testUser");

mockery.checking(new Expectations() {
{
oneOf(identityStoreHandler).validate(credential);
will(returnValue(validResult));
}
});

AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
credential, identityStoreHandler);

assertEquals("Should return SUCCESS for valid credentials",
AuthenticationStatus.SUCCESS, status);
assertNotNull("Client subject should be populated", clientSubject.getPrincipals());
}

/**
* Normal operation: skipIdentityStores is false and invalid credentials,
* so return NOT_DONE.
*/
@Test
public void testValidateWithIdentityStore_SkipIdentityStoresFalse_InvalidCredentials() {
final CredentialValidationResult invalidResult = CredentialValidationResult.INVALID_RESULT;

mockery.checking(new Expectations() {
{
oneOf(identityStoreHandler).validate(credential);
will(returnValue(invalidResult));
}
});

AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
credential, identityStoreHandler);

assertEquals("Should return SEND_FAILURE for invalid credentials",
AuthenticationStatus.SEND_FAILURE, status);
}

/**
* Normal operation: skipIdentityStores is false, so return NOT_DONE.
*/
@Test
public void testValidateWithIdentityStore_SkipIdentityStoresFalse_NotValidated() {
final CredentialValidationResult notValidatedResult = CredentialValidationResult.NOT_VALIDATED_RESULT;

mockery.checking(new Expectations() {
{
oneOf(identityStoreHandler).validate(credential);
will(returnValue(notValidatedResult));
}
});

AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
credential, identityStoreHandler);

assertEquals("Should return NOT_DONE when not validated",
AuthenticationStatus.NOT_DONE, status);
}

/**
* Test validateWithIdentityStore handles null identity store.
*/
@Test(expected = NullPointerException.class)
public void testValidateWithIdentityStore_NullHandler() {
// will throw NullPointerException - look at the code!
utils.validateWithIdentityStore(REALM_NAME, clientSubject, credential, null);
}

/**
* Test null credential for validateWithIdentityStore.
*/
@Test
public void testValidateWithIdentityStore_NullCredential() {
mockery.checking(new Expectations() {
{
oneOf(identityStoreHandler).validate(null);
will(returnValue(CredentialValidationResult.INVALID_RESULT));
}
});

AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
null, identityStoreHandler);

assertEquals("Should return SEND_FAILURE for null credential",
AuthenticationStatus.SEND_FAILURE, status);
}

/**
* Test empty "" realm name for validateWithIdentityStore.
*/
@Test
public void testValidateWithIdentityStore_EmptyRealmName() {
final CredentialValidationResult validResult = new CredentialValidationResult("testUser");

mockery.checking(new Expectations() {
{
oneOf(identityStoreHandler).validate(credential);
will(returnValue(validResult));
}
});

AuthenticationStatus status = utils.validateWithIdentityStore("", clientSubject,
credential, identityStoreHandler);

assertEquals("Should return SUCCESS even with empty realm name",
AuthenticationStatus.SUCCESS, status);
}

/**
* Test null realm name for validateWithIdentityStore.
*/
@Test
public void testValidateWithIdentityStore_NullRealmName() {
final CredentialValidationResult validResult = new CredentialValidationResult("testUser");

mockery.checking(new Expectations() {
{
oneOf(identityStoreHandler).validate(credential);
will(returnValue(validResult));
}
});

AuthenticationStatus status = utils.validateWithIdentityStore(null, clientSubject,
credential, identityStoreHandler);

assertEquals("Should return SUCCESS even with null realm name",
AuthenticationStatus.SUCCESS, status);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,6 @@ partitionedCookie.desc=Specifies whether to partition SSO cookies with SameSite
partitionedCookieTrue=Always partition SSO cookies that have SameSite set to None.
partitionedCookieFalse=Do not partition SSO cookies.
partitionedCookieDefer=Allow the HTTP transport settings to determine whether to partition SSO cookies.

skipIdentityStores=Skip identity stores during authentication
skipIdentityStores.desc=When set to true, all identity stores (both internal and custom) are skipped and will not be used during the authentication process. Authentication will fall back to external configuration (i.e. a registry in server.xml).
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@
ibm:final="true" name="internal" description="internal use only" />
<AD id="loggedOutCookieCacheService.cardinality.minimum" type="String" default="${count(loggedOutCookieCacheRef)}"
ibm:final="true" name="internal" description="internal use only"/>

<AD id="skipIdentityStores" name="%skipIdentityStores" description="%skipIdentityStores.desc"
required="false" type="Boolean" default="false"/>

</OCD>
<Designate pid="com.ibm.ws.webcontainer.security.WebAppSecurityCollaboratorImpl">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1415,13 +1415,13 @@ private MatchResponse getMatchResponse(HttpServletRequest req, String uriName, S

protected String getApplicationName() {
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
return wmmd.getConfiguration().getApplicationName();
}

protected String getModuleName() {
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
return wmmd.getConfiguration().getModuleName();
}

Expand All @@ -1431,7 +1431,7 @@ public SecurityMetadata getSecurityMetadata() {

protected void setSecurityMetadata(SecurityMetadata secMetadata) {
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
wmmd.setSecurityMetaData(secMetadata);
}

Expand Down Expand Up @@ -1552,7 +1552,7 @@ protected WebAppConfig getWebAppConfig() {
WebAppConfig wac = null;
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
if (cmd instanceof WebComponentMetaData) { // Only get the header for web modules, i.e. not for EJB
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
wac = wmmd.getConfiguration();
if (!(wac instanceof com.ibm.ws.webcontainer.osgi.webapp.WebAppConfiguration)) {
wac = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,11 @@ public interface WebAppSecurityConfig {
boolean isUseContextRootForSSOCookiePath();

long postParamMaxRequestBodySize();

/**
* Check if identity stores should be skipped.
*
* @return true if identity stores are skipped
*/
boolean getSkipIdentityStores();
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public class WebAppSecurityConfigImpl implements WebAppSecurityConfig {
public static final String CFG_KEY_PARTITIONED_COOKIE = "partitionedCookie";
public static final String CFG_KEY_USE_CONTEXT_ROOT_FOR_SSO_COOKIE_PATH = "useContextRootForSSOCookiePath";
public static final String CFG_KEY_MAX_CONTENT_LENGTH_TO_SAVE_POST_PARAMETERS = "postParamMaxRequestBodySize";
public static final String CFG_KEY_SKIP_IDENTITY_STORES = "skipIdentityStores";

// New attributes must update getChangedProperties method
private final Boolean logoutOnHttpSessionExpire;
Expand Down Expand Up @@ -107,6 +108,7 @@ public class WebAppSecurityConfigImpl implements WebAppSecurityConfig {
private Boolean partitionedCookie = null; // in BETA, mark as final once GA'ed
private final Boolean useContextRootForSSOCookiePath;
private final Long postParamMaxRequestBodySize;
private final Boolean skipIdentityStores;

protected final AtomicServiceReference<WsLocationAdmin> locationAdminRef;
protected final AtomicServiceReference<SecurityService> securityServiceRef;
Expand Down Expand Up @@ -149,6 +151,7 @@ public class WebAppSecurityConfigImpl implements WebAppSecurityConfig {
put(CFG_KEY_PARTITIONED_COOKIE, "partitionedCookie");
put(CFG_KEY_USE_CONTEXT_ROOT_FOR_SSO_COOKIE_PATH, "useContextRootForSSOCookiePath");
put(CFG_KEY_MAX_CONTENT_LENGTH_TO_SAVE_POST_PARAMETERS, "postParamMaxRequestBodySize");
put(CFG_KEY_SKIP_IDENTITY_STORES, "skipIdentityStores");
}
};

Expand Down Expand Up @@ -194,9 +197,10 @@ public WebAppSecurityConfigImpl(Map<String, Object> newProperties,
sameSiteCookie = (String) newProperties.get(CFG_KEY_SAME_SITE_COOKIE);
useContextRootForSSOCookiePath = (Boolean) newProperties.get(CFG_KEY_USE_CONTEXT_ROOT_FOR_SSO_COOKIE_PATH);
postParamMaxRequestBodySize = (Long) newProperties.get(CFG_KEY_MAX_CONTENT_LENGTH_TO_SAVE_POST_PARAMETERS);
skipIdentityStores = (Boolean) newProperties.get(CFG_KEY_SKIP_IDENTITY_STORES);

String partValue = (String) newProperties.get(CFG_KEY_PARTITIONED_COOKIE);
if ("true".equalsIgnoreCase(partValue)||"false".equalsIgnoreCase(partValue)) {
if ("true".equalsIgnoreCase(partValue) || "false".equalsIgnoreCase(partValue)) {
// we want partitionedCookie to be null unless the value is true or false
// defer is the default value which mean that the channel config determines the partitioned value
// if the value is true / false then this config was explicltly set by the user
Expand Down Expand Up @@ -615,8 +619,8 @@ public Boolean getPartitionedCookie() {

@Override
public boolean isPartitionedCookie() {
if (partitionedCookie!=null && partitionedCookie==Boolean.TRUE) {
return true;
if (partitionedCookie != null && partitionedCookie == Boolean.TRUE) {
return true;
}
return false;
}
Expand All @@ -631,17 +635,22 @@ public long postParamMaxRequestBodySize() {
return postParamMaxRequestBodySize.longValue();
}

@Override
public boolean getSkipIdentityStores() {
return skipIdentityStores != null ? skipIdentityStores.booleanValue() : false;
}

// This method is for config users that need to know if an admin has provided a true/false or no value for a
// boolean config attribute. The current infrastructure does not properly support this
public static Boolean getBooleanValue(String attribute, String strValue) {
Boolean retVal = null;
if (strValue!=null && strValue.length()>0) {
//only values that config gives us are true/false/defer
if ("true".equalsIgnoreCase(strValue) || "false".equalsIgnoreCase(strValue)) {
retVal = Boolean.valueOf(strValue);
}
}
return(retVal);
Boolean retVal = null;
if (strValue != null && strValue.length() > 0) {
//only values that config gives us are true/false/defer
if ("true".equalsIgnoreCase(strValue) || "false".equalsIgnoreCase(strValue)) {
retVal = Boolean.valueOf(strValue);
}
}
return (retVal);
}

}
Loading