Skip to content

Commit f517c30

Browse files
authored
Merge pull request wildfly#19622 from OndrejKotek/WFLY-21444
WFLY-21444 Adding test for caching security realm
2 parents 0620a8d + 0d50b7a commit f517c30

File tree

2 files changed

+293
-0
lines changed

2 files changed

+293
-0
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
/*
2+
* Copyright The WildFly Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package org.wildfly.test.integration.elytron.realm;
6+
7+
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
8+
import static jakarta.servlet.http.HttpServletResponse.SC_OK;
9+
import static jakarta.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
10+
import static org.junit.Assert.assertEquals;
11+
12+
import java.io.File;
13+
import java.io.IOException;
14+
import java.net.URISyntaxException;
15+
import java.net.URL;
16+
import org.apache.commons.io.FileUtils;
17+
import org.jboss.arquillian.container.test.api.Deployment;
18+
import org.jboss.arquillian.container.test.api.RunAsClient;
19+
import org.jboss.arquillian.junit.Arquillian;
20+
import org.jboss.arquillian.test.api.ArquillianResource;
21+
import org.jboss.as.arquillian.api.ContainerResource;
22+
import org.jboss.as.arquillian.api.ServerSetup;
23+
import org.jboss.as.arquillian.api.ServerSetupTask;
24+
import org.jboss.as.arquillian.container.ManagementClient;
25+
import org.jboss.as.test.integration.management.util.CLIWrapper;
26+
import org.jboss.as.test.integration.security.common.Utils;
27+
import org.jboss.as.test.shared.ServerReload;
28+
import org.jboss.shrinkwrap.api.ShrinkWrap;
29+
import org.jboss.shrinkwrap.api.asset.StringAsset;
30+
import org.jboss.shrinkwrap.api.spec.WebArchive;
31+
import org.junit.Before;
32+
import org.junit.Test;
33+
import org.junit.runner.RunWith;
34+
35+
/**
36+
* Tests Elytron caching-realm backed by filesystem-realm.
37+
*
38+
* Given: application secured by caching-realm backed by filesystem-realm
39+
* and caching-realm cache has maximum size 2.
40+
*
41+
* @author Ondrej Kotek
42+
*/
43+
@RunWith(Arquillian.class)
44+
@RunAsClient
45+
@ServerSetup({CachingFilesystemRealmTestCase.SetUpTask.class})
46+
public class CachingFilesystemRealmTestCase {
47+
48+
@ContainerResource
49+
protected ManagementClient managementClient;
50+
51+
private static final String NAME = CachingFilesystemRealmTestCase.class.getSimpleName();
52+
private static final String DEPLOYMENT_NAME = "cachingFilesystemRealm";
53+
private static final String INDEX_PAGE_CONTENT = "index page content";
54+
private static final String SECURITY_DOMAIN = "securityDomain-" + NAME;
55+
private static final String CACHING_REALM = "cachingFileSystemRealm-" + NAME;
56+
private static final String FILESYSTEM_REALM = "fileSystemRealm-" + NAME;
57+
private static final String FILESYSTEM_REALM2 = "fileSystemRealm2-" + NAME;
58+
59+
private static final String USER_A = "user-a";
60+
private static final String USER_B = "user-b";
61+
private static final String USER_C = "user-c";
62+
private static final String PASSWORD_A = "passwordA";
63+
private static final String PASSWORD_B = "passwordB";
64+
private static final String PASSWORD_C = "passwordC";
65+
66+
private static final String TARGET_FOLDER = System.getProperty("project.build.directory", "target");
67+
private static final String FILESYSTEM_REALM_PATH = TARGET_FOLDER + File.separator + NAME;
68+
69+
@Deployment(name = DEPLOYMENT_NAME)
70+
public static WebArchive createDeployment() {
71+
final WebArchive war = ShrinkWrap.create(WebArchive.class, DEPLOYMENT_NAME + ".war");
72+
war.addAsWebInfResource(CachingFilesystemRealmTestCase.class.getPackage(), "caching-filesystem-realm-web.xml", "web.xml");
73+
war.addAsWebInfResource(Utils.getJBossWebXmlAsset(SECURITY_DOMAIN), "jboss-web.xml");
74+
war.add(new StringAsset(INDEX_PAGE_CONTENT), "index.html");
75+
return war;
76+
}
77+
78+
@Before
79+
public void resetEnvironment() {
80+
clearCache();
81+
changeUserPassword(USER_A, PASSWORD_A);
82+
}
83+
84+
/**
85+
* Given: client has successfully authenticated to application using USER_A credentials,
86+
* and password for USER_A is changed to "anotherPassword" on the file system.
87+
* When client does a new request using original USER_A credentials,
88+
* then authentication verification for the request should be successfully done against the cached identity
89+
* (any changes of USER_A on file system should be ignored).
90+
*
91+
* @param webAppUrl URL to application protected by caching-realm
92+
*/
93+
@Test
94+
public void testIdentityPasswordIsCached(@ArquillianResource URL webAppUrl) throws URISyntaxException, Exception {
95+
// check basic authn is required
96+
Utils.makeCall(webAppUrl.toURI(), SC_UNAUTHORIZED);
97+
98+
assertAccessAllowed(webAppUrl, USER_A, PASSWORD_A);
99+
100+
changeUserPassword(USER_A, "anotherPassword");
101+
assertAccessAllowed(webAppUrl, USER_A, PASSWORD_A);
102+
103+
clearCache();
104+
assertAccessUnauthorized(webAppUrl, USER_A, PASSWORD_A);
105+
}
106+
107+
/**
108+
* Given: client has successfully authenticated to application using USER_A credentials,
109+
* and role for USER_A is changed to "anotherRole" on the file system.
110+
* When client does a new request to a resource that requires original role using USER_A credentials,
111+
* then authorization verification for the request should be successfully performed against the cached identity
112+
* (any changes of USER_A on the file system should be ignored).
113+
*
114+
* @param webAppUrl URL to application protected by caching-realm
115+
*/
116+
@Test
117+
public void testIdentityAttributeIsCached(@ArquillianResource URL webAppUrl) {
118+
try {
119+
assertAccessAllowed(webAppUrl, USER_A, PASSWORD_A);
120+
121+
changeUserRole(USER_A, "anotherRole");
122+
assertAccessAllowed(webAppUrl, USER_A, PASSWORD_A);
123+
124+
clearCache();
125+
assertAccessForbidden(webAppUrl, USER_A, PASSWORD_A);
126+
} finally {
127+
changeUserRole(USER_A, "Admin");
128+
}
129+
}
130+
131+
/**
132+
* Given: client has successfully authenticated to application using credentials in following order:
133+
* USER_A, USER_B, USER_C,
134+
* and password for USER_A is changed to "anotherPassword" on the file system.
135+
* When client does a new request using USER_A credentials,
136+
* then authentication verification for the request should fail because it is not performed against cached identity
137+
* (any changes of USER_A on the file system should be reflected).
138+
*
139+
* @param webAppUrl URL to application protected by caching-realm
140+
*/
141+
@Test
142+
public void testJustLastUsedIdentitiesInLimitAreCached(@ArquillianResource URL webAppUrl) {
143+
assertAccessAllowed(webAppUrl, USER_A, PASSWORD_A);
144+
assertAccessAllowed(webAppUrl, USER_B, PASSWORD_B);
145+
assertAccessAllowed(webAppUrl, USER_C, PASSWORD_C);
146+
147+
changeUserPassword(USER_A, "anotherPassword");
148+
assertAccessUnauthorized(webAppUrl, USER_A, PASSWORD_A);
149+
assertAccessAllowed(webAppUrl, USER_A, "anotherPassword");
150+
}
151+
152+
private static void assertAccessAllowed(URL webAppUrl, String username, String password) {
153+
try {
154+
String response = Utils.makeCallWithBasicAuthn(webAppUrl, username, password, SC_OK);
155+
assertEquals("Response content does not match index page", response, INDEX_PAGE_CONTENT);
156+
} catch (IOException | URISyntaxException ex) {
157+
throw new IllegalStateException("Unable to call the web application", ex);
158+
}
159+
}
160+
161+
private static void assertAccessUnauthorized(URL webAppUrl, String username, String password) {
162+
try {
163+
Utils.makeCallWithBasicAuthn(webAppUrl, username, password, SC_UNAUTHORIZED);
164+
} catch (IOException | URISyntaxException ex) {
165+
throw new IllegalStateException("Unable to call the web application", ex);
166+
}
167+
}
168+
169+
private static void assertAccessForbidden(URL webAppUrl, String username, String password) {
170+
try {
171+
Utils.makeCallWithBasicAuthn(webAppUrl, username, password, SC_FORBIDDEN);
172+
} catch (IOException | URISyntaxException ex) {
173+
throw new IllegalStateException("Unable to call the web application", ex);
174+
}
175+
}
176+
177+
private static void clearCache() {
178+
try (CLIWrapper cli = new CLIWrapper(true)) {
179+
cli.sendLine(String.format("/subsystem=elytron/caching-realm=%s:clear-cache", CACHING_REALM));
180+
} catch (Exception ex) {
181+
throw new IllegalStateException("Unable to clear cache of caching-realm", ex);
182+
}
183+
}
184+
185+
// Password has to be changed through another filesystem-realm for the cache not to be updated.
186+
private static void changeUserPassword(String username, String password) {
187+
try (CLIWrapper cli = new CLIWrapper(true)) {
188+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:set-password(identity=%s, clear={password=\"%s\"})", FILESYSTEM_REALM2, username, password));
189+
} catch (Exception ex) {
190+
throw new IllegalStateException("Unable to change user password in filesystem-realm", ex);
191+
}
192+
}
193+
194+
// Role has to be changed through another filesystem-realm for the cache not to be updated.
195+
private static void changeUserRole(String username, String role) {
196+
try (CLIWrapper cli = new CLIWrapper(true)) {
197+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:remove-identity-attribute(identity=%s, name=groups)", FILESYSTEM_REALM2, username));
198+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:add-identity-attribute(identity=%s, name=groups, value=[\"%s\"])", FILESYSTEM_REALM2, username, role));
199+
} catch (Exception ex) {
200+
throw new IllegalStateException("Unable to change user role in filesystem-realm", ex);
201+
}
202+
}
203+
204+
static class SetUpTask implements ServerSetupTask {
205+
206+
static final String HTTP_AUTH_FACTORY = "httpAuthFactory-" + NAME;
207+
208+
@Override
209+
public void setup(ManagementClient managementClient, java.lang.String s) throws Exception {
210+
configureServer();
211+
prepareFilesystemUsers();
212+
}
213+
214+
@Override
215+
public void tearDown(ManagementClient managementClient, java.lang.String s) throws Exception {
216+
try (CLIWrapper cli = new CLIWrapper(true)) {
217+
cli.sendLine(String.format("/subsystem=undertow/application-security-domain=%s:remove()", SECURITY_DOMAIN));
218+
cli.sendLine(String.format("/subsystem=elytron/http-authentication-factory=%s:remove()", HTTP_AUTH_FACTORY));
219+
cli.sendLine(String.format("/subsystem=elytron/security-domain=%s:remove()", SECURITY_DOMAIN));
220+
cli.sendLine(String.format("/subsystem=elytron/caching-realm=%s:remove()", CACHING_REALM));
221+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:remove()", FILESYSTEM_REALM));
222+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:remove()", FILESYSTEM_REALM2));
223+
}
224+
ServerReload.reloadIfRequired(managementClient);
225+
FileUtils.deleteDirectory(new File(FILESYSTEM_REALM_PATH));
226+
}
227+
228+
private static void configureServer() {
229+
try (CLIWrapper cli = new CLIWrapper(true)) {
230+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:add(path=%s)", FILESYSTEM_REALM, FILESYSTEM_REALM_PATH));
231+
// Passwords and attributes have to be changed through another filesystem-realm for the cache not to be updated
232+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:add(path=%s)", FILESYSTEM_REALM2, FILESYSTEM_REALM_PATH));
233+
cli.sendLine(String.format("/subsystem=elytron/caching-realm=%s:add(realm=%s, maximum-entries=2)", CACHING_REALM, FILESYSTEM_REALM));
234+
cli.sendLine(String.format("/subsystem=elytron/security-domain=%1$s:add(realms=[{realm=%2$s, role-decoder=groups-to-roles}], "
235+
+ "default-realm=%2$s, permission-mapper=default-permission-mapper)", SECURITY_DOMAIN, CACHING_REALM));
236+
cli.sendLine(String.format("/subsystem=elytron/http-authentication-factory=%s:add(http-server-mechanism-factory=global, security-domain=%s, "
237+
+ "mechanism-configurations=[{mechanism-name=BASIC, mechanism-realm-configurations=[{realm-name=\"Some Realm\"}]}])",
238+
HTTP_AUTH_FACTORY, SECURITY_DOMAIN));
239+
cli.sendLine(String.format("/subsystem=undertow/application-security-domain=%s:add(http-authentication-factory=%s)",
240+
SECURITY_DOMAIN, HTTP_AUTH_FACTORY));
241+
} catch (Exception ex) {
242+
throw new IllegalStateException("Unable to configure server", ex);
243+
}
244+
}
245+
246+
private static void prepareFilesystemUsers() {
247+
addUserToFilesystemRealm(USER_A, PASSWORD_A, "Admin");
248+
addUserToFilesystemRealm(USER_B, PASSWORD_B, "User");
249+
addUserToFilesystemRealm(USER_C, PASSWORD_C, "Admin");
250+
}
251+
252+
private static void addUserToFilesystemRealm(String username, String password, String role) {
253+
try (CLIWrapper cli = new CLIWrapper(true)) {
254+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:add-identity(identity=%s)", FILESYSTEM_REALM, username));
255+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:set-password(identity=%s, clear={password=\"%s\"})", FILESYSTEM_REALM, username, password));
256+
cli.sendLine(String.format("/subsystem=elytron/filesystem-realm=%s:add-identity-attribute(identity=%s, name=groups, value=[\"%s\"])", FILESYSTEM_REALM, username, role));
257+
} catch (Exception ex) {
258+
throw new IllegalStateException("Unable to add user to filesystem-realm", ex);
259+
}
260+
}
261+
262+
}
263+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
5+
version="6.0">
6+
7+
<security-constraint>
8+
<web-resource-collection>
9+
<web-resource-name>protected</web-resource-name>
10+
<url-pattern>/*</url-pattern>
11+
</web-resource-collection>
12+
<auth-constraint>
13+
<role-name>Admin</role-name>
14+
<role-name>User</role-name>
15+
</auth-constraint>
16+
</security-constraint>
17+
18+
<login-config>
19+
<auth-method>BASIC</auth-method>
20+
<realm-name>Test realm</realm-name>
21+
</login-config>
22+
23+
<security-role>
24+
<role-name>Admin</role-name>
25+
</security-role>
26+
<security-role>
27+
<role-name>User</role-name>
28+
</security-role>
29+
30+
</web-app>

0 commit comments

Comments
 (0)