Skip to content

Commit 3eb4616

Browse files
committed
During authentication, skip all identity stores when processing.
1 parent 5578a8e commit 3eb4616

File tree

8 files changed

+398
-17
lines changed

8 files changed

+398
-17
lines changed

dev/com.ibm.ws.security.javaeesec.cdi/src/com/ibm/ws/security/javaeesec/cdi/beans/Utils.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* are made available under the terms of the Eclipse Public License 2.0
55
* which accompanies this distribution, and is available at
66
* http://www.eclipse.org/legal/epl-2.0/
7-
*
7+
*
88
* SPDX-License-Identifier: EPL-2.0
99
*
1010
* Contributors:
@@ -100,6 +100,7 @@ public AuthenticationStatus handleAuthenticate(CDI cdi, String realmName, @Sensi
100100
}
101101

102102
public AuthenticationStatus validateWithIdentityStore(String realmName, Subject clientSubject, @Sensitive Credential credential, IdentityStoreHandler identityStoreHandler) {
103+
103104
AuthenticationStatus status = AuthenticationStatus.SEND_FAILURE;
104105
CredentialValidationResult result = identityStoreHandler.validate(credential);
105106
if (result.getStatus() == CredentialValidationResult.Status.VALID) {
@@ -111,6 +112,25 @@ public AuthenticationStatus validateWithIdentityStore(String realmName, Subject
111112
return status;
112113
}
113114

115+
/**
116+
* Check if identity stores are skipped via webAppSecurity configuration.
117+
*
118+
* @return true if skipped
119+
*/
120+
private boolean isIdentityStoresSkipped() {
121+
try {
122+
com.ibm.ws.webcontainer.security.WebAppSecurityConfig config = com.ibm.ws.webcontainer.security.util.WebConfigUtils.getWebAppSecurityConfig();
123+
if (config != null) {
124+
return config.getSkipIdentityStores();
125+
}
126+
} catch (Exception e) {
127+
if (tc.isDebugEnabled()) {
128+
Tr.debug(tc, "Error checking skipIdentityStores config", e);
129+
}
130+
}
131+
return false;
132+
}
133+
114134
private AuthenticationStatus validateWithUserRegistry(Subject clientSubject, @Sensitive Credential credential,
115135
CallbackHandler handler) throws AuthenticationException {
116136
AuthenticationStatus status = AuthenticationStatus.SEND_FAILURE;
@@ -249,6 +269,14 @@ public IdentityStoreHandler getIdentityStoreHandler(CDI cdi) {
249269

250270
@SuppressWarnings({ "unchecked", "rawtypes" })
251271
public boolean isIdentityStoreAvailable(CDI cdi) {
272+
// check if identity stores are skipped via webAppSecurity config
273+
if (isIdentityStoresSkipped()) {
274+
if (tc.isDebugEnabled()) {
275+
Tr.debug(tc, "Identity stores skipped via webAppSecurity configuration");
276+
}
277+
return false;
278+
}
279+
252280
Instance<IdentityStore> identityStoreInstances = cdi.select(IdentityStore.class);
253281
if (identityStoreInstances != null && !identityStoreInstances.isUnsatisfied() && !identityStoreInstances.isAmbiguous()) {
254282
return true;
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2024 IBM Corporation and others.
3+
* All rights reserved. This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License 2.0
5+
* which accompanies this distribution, and is available at
6+
* http://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*******************************************************************************/
10+
package com.ibm.ws.security.javaeesec.cdi.beans;
11+
12+
import static org.junit.Assert.assertEquals;
13+
import static org.junit.Assert.assertNotNull;
14+
15+
import javax.security.auth.Subject;
16+
import javax.security.enterprise.AuthenticationStatus;
17+
import javax.security.enterprise.credential.UsernamePasswordCredential;
18+
import javax.security.enterprise.identitystore.CredentialValidationResult;
19+
import javax.security.enterprise.identitystore.IdentityStoreHandler;
20+
21+
import org.jmock.Expectations;
22+
import org.jmock.Mockery;
23+
import org.jmock.integration.junit4.JUnit4Mockery;
24+
import org.junit.After;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
28+
/**
29+
* Unit tests for Utils.validateWithIdentityStore with skipIdentityStores functionality.
30+
*/
31+
public class UtilsSkipIdentityStoresTest {
32+
33+
private final Mockery mockery = new JUnit4Mockery();
34+
private Utils utils;
35+
private IdentityStoreHandler identityStoreHandler;
36+
private Subject clientSubject;
37+
private UsernamePasswordCredential credential;
38+
private static final String REALM_NAME = "testRealm";
39+
40+
@Before
41+
public void setUp() {
42+
utils = new Utils();
43+
identityStoreHandler = mockery.mock(IdentityStoreHandler.class);
44+
clientSubject = new Subject();
45+
credential = new UsernamePasswordCredential("testUser", "testPassword");
46+
}
47+
48+
@After
49+
public void tearDown() {
50+
mockery.assertIsSatisfied();
51+
}
52+
53+
/**
54+
* Normal operation: skipIdentityStores is false and valid credentials,
55+
* so return SUCCESS.
56+
*/
57+
@Test
58+
public void testValidateWithIdentityStore_SkipIdentityStoresFalse_ValidCredentials() {
59+
final CredentialValidationResult validResult = new CredentialValidationResult("testUser");
60+
61+
mockery.checking(new Expectations() {
62+
{
63+
oneOf(identityStoreHandler).validate(credential);
64+
will(returnValue(validResult));
65+
}
66+
});
67+
68+
AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
69+
credential, identityStoreHandler);
70+
71+
assertEquals("Should return SUCCESS for valid credentials",
72+
AuthenticationStatus.SUCCESS, status);
73+
assertNotNull("Client subject should be populated", clientSubject.getPrincipals());
74+
}
75+
76+
/**
77+
* Normal operation: skipIdentityStores is false and invalid credentials,
78+
* so return NOT_DONE.
79+
*/
80+
@Test
81+
public void testValidateWithIdentityStore_SkipIdentityStoresFalse_InvalidCredentials() {
82+
final CredentialValidationResult invalidResult = CredentialValidationResult.INVALID_RESULT;
83+
84+
mockery.checking(new Expectations() {
85+
{
86+
oneOf(identityStoreHandler).validate(credential);
87+
will(returnValue(invalidResult));
88+
}
89+
});
90+
91+
AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
92+
credential, identityStoreHandler);
93+
94+
assertEquals("Should return SEND_FAILURE for invalid credentials",
95+
AuthenticationStatus.SEND_FAILURE, status);
96+
}
97+
98+
/**
99+
* Normal operation: skipIdentityStores is false, so return NOT_DONE.
100+
*/
101+
@Test
102+
public void testValidateWithIdentityStore_SkipIdentityStoresFalse_NotValidated() {
103+
final CredentialValidationResult notValidatedResult = CredentialValidationResult.NOT_VALIDATED_RESULT;
104+
105+
mockery.checking(new Expectations() {
106+
{
107+
oneOf(identityStoreHandler).validate(credential);
108+
will(returnValue(notValidatedResult));
109+
}
110+
});
111+
112+
AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
113+
credential, identityStoreHandler);
114+
115+
assertEquals("Should return NOT_DONE when not validated",
116+
AuthenticationStatus.NOT_DONE, status);
117+
}
118+
119+
/**
120+
* Test validateWithIdentityStore handles null identity store.
121+
*/
122+
@Test(expected = NullPointerException.class)
123+
public void testValidateWithIdentityStore_NullHandler() {
124+
// will throw NullPointerException - look at the code!
125+
utils.validateWithIdentityStore(REALM_NAME, clientSubject, credential, null);
126+
}
127+
128+
/**
129+
* Test null credential for validateWithIdentityStore.
130+
*/
131+
@Test
132+
public void testValidateWithIdentityStore_NullCredential() {
133+
mockery.checking(new Expectations() {
134+
{
135+
oneOf(identityStoreHandler).validate(null);
136+
will(returnValue(CredentialValidationResult.INVALID_RESULT));
137+
}
138+
});
139+
140+
AuthenticationStatus status = utils.validateWithIdentityStore(REALM_NAME, clientSubject,
141+
null, identityStoreHandler);
142+
143+
assertEquals("Should return SEND_FAILURE for null credential",
144+
AuthenticationStatus.SEND_FAILURE, status);
145+
}
146+
147+
/**
148+
* Test empty "" realm name for validateWithIdentityStore.
149+
*/
150+
@Test
151+
public void testValidateWithIdentityStore_EmptyRealmName() {
152+
final CredentialValidationResult validResult = new CredentialValidationResult("testUser");
153+
154+
mockery.checking(new Expectations() {
155+
{
156+
oneOf(identityStoreHandler).validate(credential);
157+
will(returnValue(validResult));
158+
}
159+
});
160+
161+
AuthenticationStatus status = utils.validateWithIdentityStore("", clientSubject,
162+
credential, identityStoreHandler);
163+
164+
assertEquals("Should return SUCCESS even with empty realm name",
165+
AuthenticationStatus.SUCCESS, status);
166+
}
167+
168+
/**
169+
* Test null realm name for validateWithIdentityStore.
170+
*/
171+
@Test
172+
public void testValidateWithIdentityStore_NullRealmName() {
173+
final CredentialValidationResult validResult = new CredentialValidationResult("testUser");
174+
175+
mockery.checking(new Expectations() {
176+
{
177+
oneOf(identityStoreHandler).validate(credential);
178+
will(returnValue(validResult));
179+
}
180+
});
181+
182+
AuthenticationStatus status = utils.validateWithIdentityStore(null, clientSubject,
183+
credential, identityStoreHandler);
184+
185+
assertEquals("Should return SUCCESS even with null realm name",
186+
AuthenticationStatus.SUCCESS, status);
187+
}
188+
}

dev/com.ibm.ws.webcontainer.security.app/resources/OSGI-INF/l10n/metatype.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,6 @@ partitionedCookie.desc=Specifies whether to partition SSO cookies with SameSite
126126
partitionedCookieTrue=Always partition SSO cookies that have SameSite set to None.
127127
partitionedCookieFalse=Do not partition SSO cookies.
128128
partitionedCookieDefer=Allow the HTTP transport settings to determine whether to partition SSO cookies.
129+
130+
skipIdentityStores=Skip Identity Stores
131+
skipIdentityStores.desc=When set to true, all identity stores (both internal and custom) are skipped and will not be used for authentication. Authentication will fall back to external configuration (i.e. a registry in serer.xml).

dev/com.ibm.ws.webcontainer.security.app/resources/OSGI-INF/metatype/metatype.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@
159159
ibm:final="true" name="internal" description="internal use only" />
160160
<AD id="loggedOutCookieCacheService.cardinality.minimum" type="String" default="${count(loggedOutCookieCacheRef)}"
161161
ibm:final="true" name="internal" description="internal use only"/>
162+
163+
<AD id="skipIdentityStores" name="%skipIdentityStores" description="%skipIdentityStores.desc"
164+
required="false" type="Boolean" default="false"/>
162165

163166
</OCD>
164167
<Designate pid="com.ibm.ws.webcontainer.security.WebAppSecurityCollaboratorImpl">

dev/com.ibm.ws.webcontainer.security/src/com/ibm/ws/webcontainer/security/WebAppSecurityCollaboratorImpl.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,7 @@ private void performSecurityChecks(HttpServletRequest req, HttpServletResponse r
679679
/**
680680
* JASPI spec requires that we check JASPI for every request, protected or
681681
* unprotected, SSO token or no SSO token, etc. which means we can't do our
682-
* normal processing.
682+
* normal processing. - DAVE
683683
*/
684684
if (isJaspiEnabled &&
685685
((JaspiService) webAuthenticatorRef.getService(JASPI_SERVICE_COMPONENT_NAME)).isAnyProviderRegistered(webRequest)) {
@@ -1415,13 +1415,13 @@ private MatchResponse getMatchResponse(HttpServletRequest req, String uriName, S
14151415

14161416
protected String getApplicationName() {
14171417
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
1418-
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
1418+
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
14191419
return wmmd.getConfiguration().getApplicationName();
14201420
}
14211421

14221422
protected String getModuleName() {
14231423
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
1424-
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
1424+
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
14251425
return wmmd.getConfiguration().getModuleName();
14261426
}
14271427

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

14321432
protected void setSecurityMetadata(SecurityMetadata secMetadata) {
14331433
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
1434-
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
1434+
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
14351435
wmmd.setSecurityMetaData(secMetadata);
14361436
}
14371437

@@ -1552,7 +1552,7 @@ protected WebAppConfig getWebAppConfig() {
15521552
WebAppConfig wac = null;
15531553
ComponentMetaData cmd = ComponentMetaDataAccessorImpl.getComponentMetaDataAccessor().getComponentMetaData();
15541554
if (cmd instanceof WebComponentMetaData) { // Only get the header for web modules, i.e. not for EJB
1555-
WebModuleMetaData wmmd = (WebModuleMetaData) ((WebComponentMetaData) cmd).getModuleMetaData();
1555+
WebModuleMetaData wmmd = (WebModuleMetaData) cmd.getModuleMetaData();
15561556
wac = wmmd.getConfiguration();
15571557
if (!(wac instanceof com.ibm.ws.webcontainer.osgi.webapp.WebAppConfiguration)) {
15581558
wac = null;

dev/com.ibm.ws.webcontainer.security/src/com/ibm/ws/webcontainer/security/WebAppSecurityConfig.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,11 @@ public interface WebAppSecurityConfig {
162162
boolean isUseContextRootForSSOCookiePath();
163163

164164
long postParamMaxRequestBodySize();
165+
166+
/**
167+
* Check if identity stores should be skipped.
168+
*
169+
* @return true if identity stores are skipped
170+
*/
171+
boolean getSkipIdentityStores();
165172
}

dev/com.ibm.ws.webcontainer.security/src/com/ibm/ws/webcontainer/security/internal/WebAppSecurityConfigImpl.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ public class WebAppSecurityConfigImpl implements WebAppSecurityConfig {
7373
public static final String CFG_KEY_PARTITIONED_COOKIE = "partitionedCookie";
7474
public static final String CFG_KEY_USE_CONTEXT_ROOT_FOR_SSO_COOKIE_PATH = "useContextRootForSSOCookiePath";
7575
public static final String CFG_KEY_MAX_CONTENT_LENGTH_TO_SAVE_POST_PARAMETERS = "postParamMaxRequestBodySize";
76+
public static final String CFG_KEY_SKIP_IDENTITY_STORES = "skipIdentityStores";
7677

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

111113
protected final AtomicServiceReference<WsLocationAdmin> locationAdminRef;
112114
protected final AtomicServiceReference<SecurityService> securityServiceRef;
@@ -149,6 +151,7 @@ public class WebAppSecurityConfigImpl implements WebAppSecurityConfig {
149151
put(CFG_KEY_PARTITIONED_COOKIE, "partitionedCookie");
150152
put(CFG_KEY_USE_CONTEXT_ROOT_FOR_SSO_COOKIE_PATH, "useContextRootForSSOCookiePath");
151153
put(CFG_KEY_MAX_CONTENT_LENGTH_TO_SAVE_POST_PARAMETERS, "postParamMaxRequestBodySize");
154+
put(CFG_KEY_SKIP_IDENTITY_STORES, "skipIdentityStores");
152155
}
153156
};
154157

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

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

616620
@Override
617621
public boolean isPartitionedCookie() {
618-
if (partitionedCookie!=null && partitionedCookie==Boolean.TRUE) {
619-
return true;
622+
if (partitionedCookie != null && partitionedCookie == Boolean.TRUE) {
623+
return true;
620624
}
621625
return false;
622626
}
@@ -631,17 +635,22 @@ public long postParamMaxRequestBodySize() {
631635
return postParamMaxRequestBodySize.longValue();
632636
}
633637

638+
@Override
639+
public boolean getSkipIdentityStores() {
640+
return skipIdentityStores != null ? skipIdentityStores.booleanValue() : false;
641+
}
642+
634643
// This method is for config users that need to know if an admin has provided a true/false or no value for a
635644
// boolean config attribute. The current infrastructure does not properly support this
636645
public static Boolean getBooleanValue(String attribute, String strValue) {
637-
Boolean retVal = null;
638-
if (strValue!=null && strValue.length()>0) {
639-
//only values that config gives us are true/false/defer
640-
if ("true".equalsIgnoreCase(strValue) || "false".equalsIgnoreCase(strValue)) {
641-
retVal = Boolean.valueOf(strValue);
642-
}
643-
}
644-
return(retVal);
646+
Boolean retVal = null;
647+
if (strValue != null && strValue.length() > 0) {
648+
//only values that config gives us are true/false/defer
649+
if ("true".equalsIgnoreCase(strValue) || "false".equalsIgnoreCase(strValue)) {
650+
retVal = Boolean.valueOf(strValue);
651+
}
652+
}
653+
return (retVal);
645654
}
646655

647656
}

0 commit comments

Comments
 (0)