Skip to content

Commit cc66031

Browse files
authored
Merge pull request #26006 from arjantijms/90_jwt_roles
2 parents 542e734 + 10c38cb commit cc66031

18 files changed

Lines changed: 422 additions & 222 deletions

File tree

appserver/admingui/common/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@
114114
<scope>provided</scope>
115115
</dependency>
116116
<dependency>
117-
<groupId>jakarta.enterprise</groupId>
118-
<artifactId>jakarta.enterprise.cdi-api</artifactId>
117+
<groupId>jakarta.cdi</groupId>
118+
<artifactId>jakarta.cdi-api</artifactId>
119119
<scope>provided</scope>
120120
</dependency>
121121
<dependency>

appserver/deployment/dol/src/main/java/com/sun/enterprise/deployment/WebBundleDescriptor.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
import java.util.Set;
5151

5252
import org.glassfish.api.deployment.archive.ArchiveType;
53-
import org.glassfish.api.event.EventTypes;
5453
import org.glassfish.deployment.common.Descriptor;
5554
import org.glassfish.grizzly.http.util.MimeType;
5655

@@ -67,10 +66,6 @@ public abstract class WebBundleDescriptor extends CommonResourceBundleDescriptor
6766

6867
private static final String DEPLOYMENT_DESCRIPTOR_DIR = "WEB-INF";
6968

70-
/** Used by the deployer and the web container */
71-
public static final EventTypes<WebBundleDescriptor> AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT = EventTypes
72-
.create("After_Servlet_Context_Initialized", WebBundleDescriptor.class);
73-
7469
private String contextRoot;
7570
private boolean denyUncoveredHttpMethods;
7671
private boolean distributable;
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.sun.enterprise.deployment;
2+
3+
import jakarta.servlet.ServletContext;
4+
5+
import org.glassfish.api.event.EventTypes;
6+
7+
public record WebBundleRuntimeContext(
8+
WebBundleDescriptor webBundleDescriptor,
9+
ServletContext servletContext) {
10+
11+
/** Used by the deployer and the web container */
12+
public static final EventTypes<WebBundleRuntimeContext> AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT =
13+
EventTypes.create("After_Servlet_Context_Initialized", WebBundleRuntimeContext.class);
14+
15+
public static final EventTypes<WebBundleRuntimeContext> AFTER_SERVLET_LOAD_INITIALIZED_EVENT =
16+
EventTypes.create("After_Servlet_Load_Initialized", WebBundleRuntimeContext.class);
17+
}

appserver/security/core-ee/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
<artifactId>jakarta.authorization-api</artifactId>
7070
</dependency>
7171

72+
<dependency>
73+
<groupId>org.glassfish.soteria</groupId>
74+
<artifactId>soteria</artifactId>
75+
</dependency>
7276
<dependency>
7377
<groupId>org.glassfish.exousia</groupId>
7478
<artifactId>exousia</artifactId>

appserver/security/core-ee/src/main/java/com/sun/enterprise/security/ee/SecurityDeployer.java

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.sun.enterprise.deployment.Application;
2121
import com.sun.enterprise.deployment.EjbBundleDescriptor;
2222
import com.sun.enterprise.deployment.WebBundleDescriptor;
23+
import com.sun.enterprise.deployment.WebBundleRuntimeContext;
2324
import com.sun.enterprise.deployment.web.LoginConfiguration;
2425
import com.sun.enterprise.security.AppCNonceCacheMap;
2526
import com.sun.enterprise.security.CNonceCacheFactory;
@@ -33,6 +34,7 @@
3334
import jakarta.inject.Inject;
3435
import jakarta.inject.Named;
3536
import jakarta.inject.Provider;
37+
import jakarta.servlet.ServletContext;
3638

3739
import java.util.ArrayList;
3840
import java.util.Collection;
@@ -57,9 +59,11 @@
5759
import org.glassfish.internal.data.ModuleInfo;
5860
import org.glassfish.security.common.CNonceCache;
5961
import org.glassfish.security.common.HAUtil;
62+
import org.glassfish.soteria.rest.RestPermissions;
6063
import org.jvnet.hk2.annotations.Service;
6164

62-
import static com.sun.enterprise.deployment.WebBundleDescriptor.AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT;
65+
import static com.sun.enterprise.deployment.WebBundleRuntimeContext.AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT;
66+
import static com.sun.enterprise.deployment.WebBundleRuntimeContext.AFTER_SERVLET_LOAD_INITIALIZED_EVENT;
6367
import static com.sun.enterprise.security.ee.authorization.AuthorizationUtil.getContextID;
6468
import static com.sun.enterprise.security.ee.authorization.AuthorizationUtil.removeRoleMapper;
6569
import static com.sun.enterprise.util.Utility.isEmpty;
@@ -77,6 +81,8 @@ public class SecurityDeployer extends SimpleDeployer<SecurityContainer, DummyApp
7781

7882
private static final Logger LOGGER = LogDomains.getLogger(SecurityDeployer.class, LogDomains.SECURITY_LOGGER);
7983

84+
private static final String WEBBUNDLE_KEY = "org.glassfish.web.deployment.descriptor.WebBundleDescriptorImpl";
85+
8086
@Inject
8187
private ServerContext serverContext;
8288

@@ -115,15 +121,31 @@ private class AppDeployEventListener implements EventListener {
115121
public void event(Event<?> event) {
116122
Application application;
117123

124+
// Overview of the events regarding to the Policy permissions:
125+
//
126+
// MODULE_LOADED / generateArtifacts
127+
// -> initial policy translation without ServletContext
128+
//
129+
// APPLICATION_LOADED
130+
// -> link web/EJB policies
131+
// -> commit EJB policies
132+
//
133+
// AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT
134+
// -> if ServletContextListeners modified security, retranslate + commit
135+
//
136+
// AFTER_SERVLET_LOAD_INITIALIZED_EVENT
137+
// -> if Jersey/Soteria staged REST permissions, retranslate + final commit
138+
118139
if (MODULE_LOADED.equals(event.type())) {
119140
ModuleInfo moduleInfo = (ModuleInfo) event.hook();
120141
if (moduleInfo instanceof ApplicationInfo) {
121142
return;
122143
}
123144

124-
WebBundleDescriptor webBundleDescriptor = (WebBundleDescriptor)
125-
moduleInfo.getMetaData("org.glassfish.web.deployment.descriptor.WebBundleDescriptorImpl");
126-
loadWebPolicy(webBundleDescriptor, false);
145+
loadWebPolicy(
146+
null,
147+
(WebBundleDescriptor) moduleInfo.getMetaData(WEBBUNDLE_KEY),
148+
false);
127149

128150
} else if (APPLICATION_LOADED.equals(event.type())) {
129151
ApplicationInfo applicationInfo = (ApplicationInfo) event.hook();
@@ -144,8 +166,8 @@ public void event(Event<?> event) {
144166
handler.register();
145167
}
146168
}
147-
} else if (AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT.equals(event.type())) {
148-
commitWebPolicy((WebBundleDescriptor) event.hook());
169+
} else if (AFTER_SERVLET_LOAD_INITIALIZED_EVENT.equals(event.type()) || AFTER_SERVLET_CONTEXT_INITIALIZED_EVENT.equals(event.type())) {
170+
commitWebPolicy((WebBundleRuntimeContext) event.hook());
149171
}
150172
}
151173
}
@@ -168,21 +190,22 @@ protected void generateArtifacts(DeploymentContext deploymentContext) throws Dep
168190
return;
169191
}
170192

171-
String applicationName = params.name();
172193
try {
173-
Application application = deploymentContext.getModuleMetaData(Application.class);
174-
Set<WebBundleDescriptor> webBundleDescriptors = application.getBundleDescriptors(WebBundleDescriptor.class);
194+
Set<WebBundleDescriptor> webBundleDescriptors =
195+
deploymentContext.getModuleMetaData(Application.class)
196+
.getBundleDescriptors(WebBundleDescriptor.class);
197+
175198
if (webBundleDescriptors == null) {
176199
return;
177200
}
178201

179202
for (WebBundleDescriptor webBundleDescriptor : webBundleDescriptors) {
180203
webBundleDescriptor.setApplicationClassLoader(deploymentContext.getFinalClassLoader());
181-
loadWebPolicy(webBundleDescriptor, false);
204+
loadWebPolicy(null, webBundleDescriptor, false);
182205
}
183206

184207
} catch (Exception se) {
185-
throw new DeploymentException("Error in generating security policy for " + applicationName, se);
208+
throw new DeploymentException("Error in generating security policy for " + params.name(), se);
186209
}
187210
}
188211

@@ -231,7 +254,7 @@ public void unload(DummyApplication container, DeploymentContext context) {
231254
* @param remove boolean indicated whether any existing policy statements are removed form context before translation
232255
* @throws DeploymentException
233256
*/
234-
private void loadWebPolicy(WebBundleDescriptor webBundleDescriptor, boolean remove) throws DeploymentException {
257+
private void loadWebPolicy(ServletContext servletContext, WebBundleDescriptor webBundleDescriptor, boolean remove) throws DeploymentException {
235258
try {
236259
if (webBundleDescriptor != null) {
237260
if (remove) {
@@ -241,7 +264,7 @@ private void loadWebPolicy(WebBundleDescriptor webBundleDescriptor, boolean remo
241264
webSecurityManager.release();
242265
}
243266
}
244-
webSecurityManagerFactory.createManager(webBundleDescriptor, true, serverContext);
267+
webSecurityManagerFactory.createManager(servletContext, webBundleDescriptor, true, serverContext);
245268
}
246269

247270
} catch (Exception se) {
@@ -251,17 +274,34 @@ private void loadWebPolicy(WebBundleDescriptor webBundleDescriptor, boolean remo
251274
}
252275

253276
/**
254-
* Puts Web Bundle Policy In Service, repeats translation if Descriptor indicates policy was changed by ContextListener.
277+
* Commits the web policy at servlet lifecycle milestones.
255278
*
256-
* @param webBundleDescriptor
279+
* <p>
280+
* This method may commit more than once during startup. The first commit, after
281+
* ServletContextListener processing, makes the policy available to subsequent
282+
* initialization code such as filter init and load-on-startup servlet init.
283+
*
284+
* <p>
285+
* Later, load-on-startup servlet initialization may discover additional security
286+
* metadata, notably Jakarta REST endpoint security staged by Soteria. If so, the
287+
* policy is reopened, rebuilt from the full effective web model, and committed
288+
* again before the module is made available for requests.
289+
*
290+
* @param webBundleRuntimeContext
257291
* @throws DeploymentException
258292
*/
259-
private void commitWebPolicy(WebBundleDescriptor webBundleDescriptor) throws DeploymentException {
293+
private void commitWebPolicy(WebBundleRuntimeContext webBundleRuntimeContext) throws DeploymentException {
294+
WebBundleDescriptor webBundleDescriptor = webBundleRuntimeContext.webBundleDescriptor();
295+
ServletContext servletContext = webBundleRuntimeContext.servletContext();
296+
260297
try {
261298
if (webBundleDescriptor != null) {
262-
if (webBundleDescriptor.isPolicyModified()) {
299+
if (webBundleDescriptor.isPolicyModified() || RestPermissions.hasPermissions(servletContext)) {
263300
// Redo policy translation for web module
264-
loadWebPolicy(webBundleDescriptor, true);
301+
loadWebPolicy(servletContext, webBundleDescriptor, true);
302+
303+
webBundleDescriptor.setPolicyModified(false);
304+
RestPermissions.clear(servletContext);
265305
}
266306

267307
String contextId = getContextID(webBundleDescriptor);

appserver/security/core-ee/src/main/java/com/sun/enterprise/security/ee/authorization/GlassFishAuthorizationService.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import jakarta.security.jacc.PolicyFactory;
3636
import jakarta.security.jacc.WebResourcePermission;
3737
import jakarta.security.jacc.WebUserDataPermission;
38+
import jakarta.servlet.ServletContext;
3839
import jakarta.servlet.http.HttpServletRequest;
3940

4041
import java.security.Permission;
@@ -48,6 +49,7 @@
4849

4950
import static com.sun.enterprise.security.ee.authorization.GlassFishToExousiaConverter.getConstraintsFromBundle;
5051
import static com.sun.enterprise.security.ee.authorization.GlassFishToExousiaConverter.getSecurityRoleRefsFromBundle;
52+
import static com.sun.enterprise.security.ee.authorization.SoteriaToExousiaConverter.getStagedPermissionsFromContext;
5153
import static com.sun.enterprise.security.ee.authorization.cache.PermissionCacheFactory.createPermissionCache;
5254
import static java.util.logging.Level.FINE;
5355
import static java.util.stream.Collectors.toSet;
@@ -96,7 +98,7 @@ public class GlassFishAuthorizationService {
9698
private final ThreadLocal<HttpServletRequest> currentRequest = new ThreadLocal<>();
9799
private final AuthorizationService exousiaAuthorizationService;
98100

99-
public GlassFishAuthorizationService(WebBundleDescriptor webBundleDescriptor, boolean register) throws PolicyContextException {
101+
public GlassFishAuthorizationService(ServletContext servletContext, WebBundleDescriptor webBundleDescriptor, boolean register) throws PolicyContextException {
100102
this.register = register;
101103
this.contextId = AuthorizationUtil.getContextID(webBundleDescriptor);
102104

@@ -105,6 +107,9 @@ public GlassFishAuthorizationService(WebBundleDescriptor webBundleDescriptor, bo
105107

106108
initPermissionCache();
107109

110+
// Check if the user has specified alternative implementations for the
111+
// PolicyConfigurationFactory and PolicyFactory via a web.xml parameter
112+
108113
webBundleDescriptor.getContextParameters()
109114
.stream()
110115
.filter(param -> param.getName().equals(PolicyConfigurationFactory.FACTORY_NAME))
@@ -119,22 +124,41 @@ public GlassFishAuthorizationService(WebBundleDescriptor webBundleDescriptor, bo
119124
.map(param -> loadFactory(webBundleDescriptor, param.getValue()))
120125
.ifPresent(clazz -> installPolicyFactory(webBundleDescriptor, clazz));
121126

127+
// Create the Exousia AuthorizationService. This implements the Jakarta Authorization
128+
// APIs.
129+
//
130+
// We provide it with a way to get hold of the Subject, which is GF specific, and an
131+
// implementation of the principal mapper (role mapper) which is also GF specific.
122132
exousiaAuthorizationService = new AuthorizationService(
123133
contextId,
124134
() -> SecurityContext.getCurrent().getSubject(),
125135
() -> new GlassFishPrincipalMapper(contextId));
126136

127137
exousiaAuthorizationService.setConstrainedUriRequestAttribute(CONSTRAINT_URI);
138+
139+
// For the web / servlet environment we additionally provide Exousia with a way
140+
// to obtain the HTTPServletRequest for a given invocation
128141
exousiaAuthorizationService.setRequestSupplier(contextId,
129142
() -> currentRequest.get());
130143

131144
exousiaAuthorizationService.addConstraintsToPolicy(
145+
// Permissions collected and staged by Soteria from REST endpoints
146+
getStagedPermissionsFromContext(servletContext),
147+
148+
// Constraints collected by GlassFish / Catalina from Servlets and web.xml
132149
getConstraintsFromBundle(webBundleDescriptor),
150+
151+
// All declared roles; discovered by GlassFish and additionally by Soteria
133152
webBundleDescriptor.getRoles()
134153
.stream()
135154
.map(e -> e.getName())
136155
.collect(toSet()),
156+
157+
// The web.xml directive to set whether HTTP methods (GET, POST, etc)
158+
// which are not explicitly defined are denied / excluded or not.
137159
webBundleDescriptor.isDenyUncoveredHttpMethods(),
160+
161+
// Role-reffing; ancient feature where Servlets can define role aliases.
138162
getSecurityRoleRefsFromBundle(webBundleDescriptor));
139163
}
140164

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (c) 2026 Contributors to the Eclipse Foundation.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0, which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the
10+
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11+
* version 2 with the GNU Classpath Exception, which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
*/
16+
package com.sun.enterprise.security.ee.authorization;
17+
18+
import jakarta.servlet.ServletContext;
19+
20+
import java.security.Permissions;
21+
22+
import org.glassfish.exousia.permissions.JakartaPermissions;
23+
import org.glassfish.soteria.rest.RestPermissions;
24+
25+
/**
26+
* This class converts from staged permissions by Soteria to Exousia JakartaPermissions.
27+
*
28+
* <p>
29+
* Soteria stages permissions discovered from Jakarta REST endpoints in the ServletContext.
30+
* Using its accessor for these, we retrieve them and creata an Exousia native {@link JakartaPermissions}
31+
* instance out of those.
32+
*
33+
*/
34+
public class SoteriaToExousiaConverter {
35+
36+
public static JakartaPermissions getStagedPermissionsFromContext(ServletContext servletContext) {
37+
JakartaPermissions jakartaPermissions = new JakartaPermissions();
38+
39+
if (servletContext != null && RestPermissions.hasPermissions(servletContext)) {
40+
41+
var excluded = RestPermissions.getExcluded(servletContext);
42+
if (excluded != null) {
43+
excluded.elementsAsStream()
44+
.forEach(e -> jakartaPermissions.getExcluded().add(e));
45+
}
46+
47+
var unchecked = RestPermissions.getUnchecked(servletContext);
48+
if (unchecked != null) {
49+
unchecked.elementsAsStream()
50+
.forEach(e -> jakartaPermissions.getUnchecked().add(e));
51+
}
52+
53+
var perRole = RestPermissions.getPerRole(servletContext);
54+
if (perRole != null) {
55+
perRole.entrySet().stream()
56+
.forEach(e -> jakartaPermissions.getPerRole().put(e.getKey(), copy(e.getValue())));
57+
}
58+
}
59+
60+
return jakartaPermissions;
61+
}
62+
63+
private static Permissions copy(Permissions source) {
64+
Permissions target = new Permissions();
65+
66+
if (source != null) {
67+
source.elementsAsStream()
68+
.forEach(target::add);
69+
}
70+
71+
return target;
72+
}
73+
}

0 commit comments

Comments
 (0)