
com.sun.web.security.RealmAdapter Maven / Gradle / Ivy
/*
* Copyright 2021, 2023 Contributors to the Eclipse Foundation.
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.web.security;
import static com.sun.enterprise.security.auth.digest.api.Constants.A1;
import static com.sun.enterprise.security.ee.authentication.glassfish.digest.impl.DigestParameterGenerator.HTTP_DIGEST;
import static com.sun.enterprise.security.ee.jmac.AuthMessagePolicy.WEB_BUNDLE;
import static com.sun.enterprise.security.ee.web.integration.WebSecurityManager.getContextID;
import static com.sun.enterprise.util.Utility.isAllNull;
import static com.sun.enterprise.util.Utility.isAnyNull;
import static com.sun.enterprise.util.Utility.isEmpty;
import static com.sun.web.security.WebSecurityResourceBundle.BUNDLE_NAME;
import static com.sun.web.security.WebSecurityResourceBundle.MSG_FORBIDDEN;
import static com.sun.web.security.WebSecurityResourceBundle.MSG_INVALID_REQUEST;
import static com.sun.web.security.WebSecurityResourceBundle.MSG_MISSING_HOST_HEADER;
import static com.sun.web.security.WebSecurityResourceBundle.MSG_NO_WEB_SECURITY_MGR;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
import static jakarta.servlet.http.HttpServletResponse.SC_SERVICE_UNAVAILABLE;
import static java.util.Arrays.asList;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.FINEST;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import static org.apache.catalina.ContainerEvent.AFTER_AUTHENTICATION;
import static org.apache.catalina.ContainerEvent.AFTER_LOGOUT;
import static org.apache.catalina.ContainerEvent.AFTER_POST_AUTHENTICATION;
import static org.apache.catalina.ContainerEvent.BEFORE_AUTHENTICATION;
import static org.apache.catalina.ContainerEvent.BEFORE_LOGOUT;
import static org.apache.catalina.ContainerEvent.BEFORE_POST_AUTHENTICATION;
import static org.apache.catalina.Globals.WRAPPED_REQUEST;
import static org.apache.catalina.Globals.WRAPPED_RESPONSE;
import static org.apache.catalina.realm.Constants.FORM_METHOD;
import static org.glassfish.epicyro.config.helper.HttpServletConstants.POLICY_CONTEXT;
import static org.glassfish.epicyro.config.helper.HttpServletConstants.REGISTER_SESSION;
import com.sun.enterprise.deployment.RunAsIdentityDescriptor;
import com.sun.enterprise.deployment.WebBundleDescriptor;
import com.sun.enterprise.deployment.WebComponentDescriptor;
import com.sun.enterprise.deployment.web.LoginConfiguration;
import com.sun.enterprise.security.AppCNonceCacheMap;
import com.sun.enterprise.security.CNonceCacheFactory;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.security.WebSecurityDeployerProbeProvider;
import com.sun.enterprise.security.auth.digest.api.DigestAlgorithmParameter;
import com.sun.enterprise.security.auth.digest.api.Key;
import com.sun.enterprise.security.auth.login.DigestCredentials;
import com.sun.enterprise.security.auth.login.DistinguishedPrincipalCredential;
import com.sun.enterprise.security.auth.login.LoginContextDriver;
import com.sun.enterprise.security.auth.realm.certificate.CertificateRealm;
import com.sun.enterprise.security.ee.authentication.glassfish.digest.impl.DigestParameterGenerator;
import com.sun.enterprise.security.ee.authentication.glassfish.digest.impl.HttpAlgorithmParameterImpl;
import com.sun.enterprise.security.ee.authentication.glassfish.digest.impl.NestedDigestAlgoParamImpl;
import com.sun.enterprise.security.ee.jmac.AuthMessagePolicy;
import com.sun.enterprise.security.ee.jmac.ConfigDomainParser;
import com.sun.enterprise.security.ee.jmac.callback.ServerContainerCallbackHandler;
import com.sun.enterprise.security.ee.web.integration.WebPrincipal;
import com.sun.enterprise.security.ee.web.integration.WebSecurityManager;
import com.sun.enterprise.security.ee.web.integration.WebSecurityManagerFactory;
import com.sun.enterprise.security.integration.RealmInitializer;
import com.sun.enterprise.util.net.NetUtils;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import jakarta.security.auth.message.AuthException;
import jakarta.security.auth.message.AuthStatus;
import jakarta.security.auth.message.MessageInfo;
import jakarta.security.auth.message.config.ServerAuthContext;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLEncoder;
import java.security.AccessController;
import java.security.InvalidAlgorithmParameterException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import javax.security.auth.x500.X500Principal;
import org.apache.catalina.Authenticator;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.HttpResponse;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.connector.RequestFacade;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.realm.Constants;
import org.apache.catalina.realm.RealmBase;
import org.glassfish.api.admin.ServerEnvironment;
import org.glassfish.api.invocation.ComponentInvocation;
import org.glassfish.epicyro.config.helper.Caller;
import org.glassfish.epicyro.config.helper.CallerPrincipal;
import org.glassfish.epicyro.config.helper.HttpServletConstants;
import org.glassfish.epicyro.services.BaseAuthenticationService;
import org.glassfish.epicyro.services.DefaultAuthenticationService;
import org.glassfish.grizzly.config.dom.NetworkConfig;
import org.glassfish.grizzly.config.dom.NetworkListener;
import org.glassfish.grizzly.config.dom.NetworkListeners;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.hk2.api.PostConstruct;
import org.glassfish.internal.api.ServerContext;
import org.glassfish.security.common.CNonceCache;
import org.glassfish.security.common.Group;
import org.glassfish.security.common.NonceInfo;
import org.glassfish.security.common.UserNameAndPassword;
import org.jvnet.hk2.annotations.Service;
/**
* This is the realm adapter used to authenticate users and authorize access to web resources. The authenticate method
* is called by Tomcat to authenticate users. The hasRole method is called by Tomcat during the authorization process.
*
* @author Harpreet Singh
* @author JeanFrancois Arcand
*/
@Service
@PerLookup
public class RealmAdapter extends RealmBase implements RealmInitializer, PostConstruct {
public static final String SECURITY_CONTEXT = "SecurityContext";
public static final String BASIC = "BASIC";
public static final String FORM = "FORM";
private static final Logger _logger = Logger.getLogger(RealmAdapter.class.getName(), BUNDLE_NAME);
private static final ResourceBundle resourceBundle = _logger.getResourceBundle();
@Deprecated
private static final String REGISTER_WITH_AUTHENTICATOR = "com.sun.web.RealmAdapter.register";
private static final String SERVER_AUTH_CONTEXT = "__jakarta.security.auth.message.ServerAuthContext";
private static final String MESSAGE_INFO = "__jakarta.security.auth.message.MessageInfo";
private static final WebSecurityDeployerProbeProvider websecurityProbeProvider = new WebSecurityDeployerProbeProvider();
// name of system property that can be used to define
// corresponding default provider for system apps.
private static final String SYSTEM_HTTPSERVLET_SECURITY_PROVIDER = "system_httpservlet_security_provider";
private WebBundleDescriptor webBundleDescriptor;
private HashMap runAsPrincipals;
private String realmName; // required for realm-per-app login
/**
* Descriptive information about this Realm implementation.
*/
protected static final String name = "J2EE-RI-RealmAdapter";
/**
* The context Id value needed for Jakarta Authorization
*/
private String contextId;
private Container virtualServer;
/**
* A WebSecurityManager
object associated with a CONTEXT_ID
*/
protected volatile WebSecurityManager webSecurityManager;
protected boolean isCurrentURIincluded = false;
/*
* the following fields are used to implement a bypass of FBL related targets
*/
protected final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private boolean contextEvaluated = false;
private String loginPage;
private String errorPage;
private final static SecurityConstraint[] emptyConstraints = new SecurityConstraint[] {};
/**
* the default provider id for system apps if one has been established. the default provider for system apps is
* established by defining a system property.
*/
private static String defaultSystemProviderID = getDefaultSystemProviderID();
private String moduleID;
private BaseAuthenticationService authenticationService;
@Inject
private ServerContext serverContext;
@Inject
private Provider appCNonceCacheMapProvider;
@Inject
private Provider cNonceCacheFactoryProvider;
@Inject
@Named(ServerEnvironment.DEFAULT_INSTANCE_NAME)
private NetworkConfig networkConfig;
/**
* The factory used for creating WebSecurityManager
object.
*/
@Inject
protected WebSecurityManagerFactory webSecurityManagerFactory;
private CNonceCacheFactory cNonceCacheFactory;
private CNonceCache cnonces;
private AppCNonceCacheMap haCNonceCacheMap;
private NetworkListeners networkListeners;
/**
* ThreadLocal object to keep track of the reentrancy status of each thread. It contains a byte[] object whose single
* element is either 0 (initial value or no reentrancy), or 1 (current thread is reentrant). When a thread exits the
* implies method, byte[0] is always reset to 0.
*/
private static ThreadLocal reentrancyStatus =
ThreadLocal.withInitial(() -> new byte[] { 0 });
public RealmAdapter() {
// used during Injection in WebContainer (glue code)
}
/**
* Create for Web Services Enterprise Beans endpoint authentication.
*
*
* Roles related data is not available here.
*/
public RealmAdapter(String realmName, String moduleID) {
this.realmName = realmName;
this.moduleID = moduleID;
}
@Override
public void initializeRealm(Object descriptor, String initialRealmName) {
this.webBundleDescriptor = (WebBundleDescriptor) descriptor;
realmName = findRealmName(initialRealmName);
contextId = WebSecurityManager.getContextID(webBundleDescriptor);
moduleID = webBundleDescriptor.getModuleID();
collectRunAsPrincipals();
}
/**
* Return true if Jakarta Authentication is available.
*
* @return true if Jakarta Authentication is available. 1171
*/
@Override
public boolean isSecurityExtensionEnabled(final ServletContext context) {
if (authenticationService == null) {
initAuthenticationService(context);
}
try {
return (authenticationService.getServerAuthConfig() != null);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns null 1. if there are no security constraints defined on any of the web resources within the context, or 2. if
* the target is a form login related page or target.
*
* otherwise return an empty array of SecurityConstraint.
*/
@Override
public SecurityConstraint[] findSecurityConstraints(HttpRequest request, Context context) {
return findSecurityConstraints(context);
}
/**
* Returns null 1. if there are no security constraints defined on any of the web resources within the context, or 2. if
* the target is a form login related page or target.
*
* otherwise return an empty array of SecurityConstraint.
*/
@Override
public SecurityConstraint[] findSecurityConstraints(String requestPathMB, String httpMethod, Context context) {
return findSecurityConstraints(context);
}
/**
* Enforce any user data constraint required by the security constraint guarding this request URI.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint being checked
*
* @exception IOException if an input/output error occurs
*
* @return true
if this constraint was not violated and processing should continue, or false
* if we have created a response already
*/
@Override
public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints) throws IOException {
return hasUserDataPermission(request, response, constraints, null, null);
}
/**
* Checks if the given request URI and method are the target of any user-data-constraint with a transport-guarantee of
* CONFIDENTIAL, and whether any such constraint is already satisfied.
*
* If uri and method are null, then the URI and method of the given request are checked.
*
* If a user-data-constraint exists that is not satisfied, then the given request will be redirected to HTTPS.
*
* @param request the request that may be redirected
* @param response the response that may be redirected
* @param constraints the security constraints to check against
* @param uri the request URI (minus the context path) to check
* @param method the request method to check
*
* @return true if the request URI and method are not the target of any unsatisfied user-data-constraint with a
* transport-guarantee of CONFIDENTIAL, and false if they are (in which case the given request will have been redirected
* to HTTPS)
*/
@Override
public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, String uri, String method) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getServletPath() == null) {
request.setServletPath(getResourceName(httpServletRequest.getRequestURI(), httpServletRequest.getContextPath()));
}
_logger.fine(() ->
"[Web-Security][ hasUserDataPermission ]" +
" Principal: " + httpServletRequest.getUserPrincipal() +
" ContextPath: " + httpServletRequest.getContextPath());
if (request.getRequest().isSecure()) {
_logger.fine(() -> "[Web-Security] request.getRequest().isSecure(): " + request.getRequest().isSecure());
return true;
}
WebSecurityManager webSecurityManager = getWebSecurityManager(true);
if (webSecurityManager == null) {
return false;
}
int isGranted = 0;
try {
isGranted = webSecurityManager.hasUserDataPermission(httpServletRequest, uri, method);
} catch (IllegalArgumentException e) {
// end the request after getting IllegalArgumentException while checking
// user data permission
_logger.log(WARNING, MSG_INVALID_REQUEST, e);
((HttpServletResponse) response.getResponse()).sendError(SC_BAD_REQUEST,
resourceBundle.getString(MSG_INVALID_REQUEST));
return false;
}
// Only redirect if we are sure the user will be granted.
// See bug 4947698
// This method will return:
// 1 - if granted
// 0 - if not granted
// -1 - if the current transport is not granted, but a redirection can occur
// so the grand will succeed.
if (isGranted == -1) {
_logger.fine(() -> "[Web-Security] redirecting using SSL");
return redirect(request, response);
}
if (isGranted == 0) {
((HttpServletResponse) response.getResponse()).sendError(SC_FORBIDDEN, resourceBundle.getString(MSG_FORBIDDEN));
return false;
}
return true;
}
/**
* Checks whether or not authentication is needed. When Jakarta Authentication (and by extension, Jakarta Security) is enabled,
* authentication is always needed.
*
*
* Returns an int, one of AUTHENTICATE_NOT_NEEDED, AUTHENTICATE_NEEDED,
* or AUTHENTICATED_NOT_AUTHORIZED
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint we are enforcing
* @param disableProxyCaching whether or not to disable proxy caching for protected resources.
* @param securePagesWithPragma true if we add headers which are incompatible with downloading office documents in IE
* under SSL but which fix a caching problem in Mozilla.
* @param ssoEnabled true if sso is enabled
*
* @exception IOException if an input/output error occurs
*/
@Override
public int preAuthenticateCheck(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, boolean disableProxyCaching, boolean securePagesWithPragma, boolean ssoEnabled) throws IOException {
boolean isGranted = false;
try {
if (!isRequestAuthenticated(request)) {
SecurityContext.setUnauthenticatedContext();
}
if (isJakartaAuthenticationEnabled()) {
return AUTHENTICATE_NEEDED;
}
isGranted = invokeWebSecurityManager(request, response, constraints);
} catch (IOException iex) {
throw iex;
} catch (Throwable ex) {
_logger.log(SEVERE, "Authentication passed, but authorization failed.", ex);
((HttpServletResponse) response.getResponse()).sendError(SC_SERVICE_UNAVAILABLE);
response.setDetailMessage(resourceBundle.getString(MSG_FORBIDDEN));
return AUTHENTICATED_NOT_AUTHORIZED;
}
if (isGranted) {
if (isRequestAuthenticated(request)) {
disableProxyCaching(request, response, disableProxyCaching, securePagesWithPragma);
if (ssoEnabled) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest();
if (!getWebSecurityManager(true).permitAll(httpServletRequest)) {
// create a session for protected sso association
httpServletRequest.getSession(true);
}
}
}
return AUTHENTICATE_NOT_NEEDED;
}
if (isRequestAuthenticated(request)) {
((HttpServletResponse) response.getResponse()).sendError(SC_FORBIDDEN);
response.setDetailMessage(resourceBundle.getString(MSG_FORBIDDEN));
return AUTHENTICATED_NOT_AUTHORIZED;
}
disableProxyCaching(request, response, disableProxyCaching, securePagesWithPragma);
return AUTHENTICATE_NEEDED;
}
/**
* Authenticates the user making this request, based on the specified login configuration. Return true
if
* any specified requirements have been satisfied, or false
if we have created a response challenge
* already.
*
* @param request Request we are processing
* @param response Response we are creating
* @param context The Context to which client of this class is attached.
* @param authenticantion the current authenticator.
* @exception IOException if an input/output error occurs
*/
@Override
public boolean invokeAuthenticateDelegate(HttpRequest request, HttpResponse response, Context context, Authenticator authenticator, boolean calledFromAuthenticate) throws IOException {
LoginConfig config = context.getLoginConfig();
if (isJakartaAuthenticationEnabled()) {
// Jakarta Authentication is enabled for this application
try {
context.fireContainerEvent(BEFORE_AUTHENTICATION, null);
RequestFacade requestFacade = (RequestFacade) request.getRequest();
SecurityContext.getCurrent().setSessionPrincipal(requestFacade.getRequestPrincipal());
return validate(request, response, config, authenticator, calledFromAuthenticate);
} finally {
SecurityContext.getCurrent().setSessionPrincipal(null);
context.fireContainerEvent(AFTER_AUTHENTICATION, null);
}
}
// Jakarta Authentication is not enabled. Use the current authenticator.
return ((AuthenticatorBase) authenticator).authenticate(request, response, config);
}
/**
* Return a short name for this Realm Adapter implementation.
*/
@Override
protected String getName() {
return name;
}
/**
* Return the name of the realm this RealmAdapter uses.
*
* @return realm name
*
*/
@Override
public String getRealmName() {
return realmName;
}
/**
* Sets the virtual server on which the web module (with which this RealmAdapter is associated with) has been deployed.
*
* @param container The virtual server
*/
@Override
public void setVirtualServer(Object container) {
this.virtualServer = (Container) container;
}
@Override
public void updateWebSecurityManager() {
if (webSecurityManager == null) {
webSecurityManager = getWebSecurityManager(true);
}
if (webSecurityManager != null) {
try {
webSecurityManager.release();
webSecurityManager.destroy();
} catch (Exception ex) {
ex.printStackTrace();
}
webSecurityManager = webSecurityManagerFactory.createManager(webBundleDescriptor, true, serverContext);
_logger.fine(() -> "WebSecurityManager for " + contextId + " has been updated");
}
}
/**
* Authenticates and sets the SecurityContext in the TLS.
*
* @return the authenticated principal.
* @param the user name.
* @param the password.
*/
@Override
public Principal authenticate(HttpRequest request, String username, char[] password) {
_logger.fine(() -> "Tomcat callback for authenticate user/password");
_logger.fine(() -> "usename = " + username);
if (authenticate((HttpServletRequest) request, username, password, null, null)) {
return new WebPrincipal(username, password, SecurityContext.getCurrent());
}
return null;
}
@Override
public Principal authenticate(HttpServletRequest httpServletRequest) {
DigestCredentials digestCredentials = generateDigestCredentials(httpServletRequest);
if (digestCredentials != null && authenticate(httpServletRequest, null, null, digestCredentials, null)) {
return new WebPrincipal(digestCredentials.getUserName(), (char[]) null, SecurityContext.getCurrent());
}
return null;
}
@Override
public Principal authenticate(HttpRequest request, X509Certificate certificates[]) {
if (authenticate((HttpServletRequest) request, null, null, null, certificates)) {
return new WebPrincipal(certificates, SecurityContext.getCurrent());
}
return null;
}
/**
* Perform access control based on the specified authorization constraint. Return true
if this constraint
* is satisfied and processing should continue, or false
otherwise.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraint Security constraint we are enforcing
* @param The Context to which client of this class is attached.
*
* @exception IOException if an input/output error occurs
*/
@Override
public boolean hasResourcePermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, Context context) throws IOException {
boolean isGranted = false;
try {
isGranted = invokeWebSecurityManager(request, response, constraints);
} catch (IOException iex) {
throw iex;
} catch (Throwable ex) {
_logger.log(SEVERE, "Authentication passed, but authorization failed.", ex);
((HttpServletResponse) response.getResponse()).sendError(SC_SERVICE_UNAVAILABLE);
response.setDetailMessage(resourceBundle.getString(MSG_FORBIDDEN));
return isGranted;
}
if (isGranted) {
return isGranted;
}
((HttpServletResponse) response.getResponse()).sendError(SC_FORBIDDEN);
response.setDetailMessage(resourceBundle.getString(MSG_FORBIDDEN));
// invoking secureResponse
invokePostAuthenticateDelegate(request, response, context);
return isGranted;
}
/**
* Post authentication for given request and response.
*
* @param request Request we are processing
* @param response Response we are creating
* @param context The Context to which client of this class is attached.
* @exception IOException if an input/output error occurs
*/
@Override
public boolean invokePostAuthenticateDelegate(HttpRequest request, HttpResponse response, Context context) throws IOException {
boolean result = false;
ServerAuthContext serverAuthContext = null;
try {
if (authenticationService != null) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest();
MessageInfo messageInfo = (MessageInfo) httpServletRequest.getAttribute(MESSAGE_INFO);
if (messageInfo != null) {
// Jakarta Authentication is enabled for this application
serverAuthContext = (ServerAuthContext) messageInfo.getMap().get(SERVER_AUTH_CONTEXT);
if (serverAuthContext != null) {
try {
context.fireContainerEvent(BEFORE_POST_AUTHENTICATION, null);
AuthStatus authStatus = serverAuthContext.secureResponse(messageInfo, null); // null serviceSubject
result = AuthStatus.SUCCESS.equals(authStatus);
} finally {
context.fireContainerEvent(AFTER_POST_AUTHENTICATION, null);
}
}
}
}
} catch (AuthException ex) {
throw new IOException(ex);
} finally {
if (authenticationService != null && serverAuthContext != null) {
if (request instanceof HttpRequestWrapper) {
request.removeNote(WRAPPED_REQUEST);
}
if (response instanceof HttpResponseWrapper) {
request.removeNote(WRAPPED_RESPONSE);
}
}
}
return result;
}
/**
* Check if the given principal has the provided role. Returns true if the principal has the specified role, false
* otherwise.
*
* @return true if the principal has the specified role.
* @param request Request we are processing
* @param response Response we are creating
* @param the principal
* @param the role
*/
@Override
public boolean hasRole(HttpRequest request, HttpResponse response, Principal principal, String role) {
WebSecurityManager webSecurityManager = getWebSecurityManager(true);
if (webSecurityManager == null) {
return false;
}
// add HttpResponse and HttpResponse to the parameters, and remove
// instance variable currentRequest from this class. References to
// this.currentRequest are also removed from other methods.
// String servletName = getResourceName( currentRequest.getRequestURI(),
// currentRequest.getContextPath());
String servletName = getCanonicalName(request);
boolean isGranted = webSecurityManager.hasRoleRefPermission(servletName, role, principal);
_logger.fine(() -> "Checking if servlet " + servletName + " with principal " + principal + " has role " + role + " isGranted: "
+ isGranted);
return isGranted;
}
@Override
public void destroy() {
super.destroy();
if (authenticationService != null) {
authenticationService.disable();
}
}
public WebBundleDescriptor getWebDescriptor() {
return webBundleDescriptor;
}
// utility method to get web security anager.
// will log warning if the manager is not found in the factory, and
// logNull is true.
// Note: webSecurityManagerFactory can be null the very questionable SOAP code just
// instantiates a RealmAdapter
public WebSecurityManager getWebSecurityManager(boolean logNull) {
if (webSecurityManager == null && webSecurityManagerFactory != null) {
synchronized (this) {
webSecurityManager = webSecurityManagerFactory.getManager(contextId);
}
if (webSecurityManager == null && logNull) {
_logger.log(WARNING, MSG_NO_WEB_SECURITY_MGR, contextId);
}
}
return webSecurityManager;
}
public boolean hasRole(String servletName, Principal principal, String role) {
WebSecurityManager secMgr = getWebSecurityManager(true);
if (secMgr == null) {
return false;
}
return secMgr.hasRoleRefPermission(servletName, role, principal);
}
@Override
public void logout(HttpRequest httpRequest) {
boolean securityExtensionEnabled = isSecurityExtensionEnabled(httpRequest.getRequest().getServletContext());
byte[] alreadyCalled = reentrancyStatus.get();
if (securityExtensionEnabled && authenticationService != null && alreadyCalled[0] == 0) {
alreadyCalled[0] = 1;
MessageInfo messageInfo = (MessageInfo) httpRequest.getRequest().getAttribute(MESSAGE_INFO);
if (messageInfo == null) {
messageInfo = new HttpMessageInfo((HttpServletRequest) httpRequest.getRequest(),
(HttpServletResponse) httpRequest.getResponse().getResponse());
}
messageInfo.getMap().put(HttpServletConstants.IS_MANDATORY, Boolean.TRUE.toString());
try {
ServerAuthContext serverAuthContext = authenticationService.getServerAuthContext(messageInfo, null);
if (serverAuthContext != null) {
/*
* Check for the default/server-generated/unauthenticated security context.
*/
SecurityContext securityContext = SecurityContext.getCurrent();
Subject subject = securityContext.didServerGenerateCredentials() ? new Subject() : securityContext.getSubject();
if (subject == null) {
subject = new Subject();
}
if (subject.isReadOnly()) {
_logger.log(WARNING, "Read-only subject found during logout processing");
}
try {
httpRequest.getContext().fireContainerEvent(BEFORE_LOGOUT, null);
serverAuthContext.cleanSubject(messageInfo, subject);
} finally {
httpRequest.getContext().fireContainerEvent(AFTER_LOGOUT, null);
}
}
} catch (AuthException ex) {
throw new RuntimeException(ex);
} finally {
doLogout(httpRequest, true);
alreadyCalled[0] = 0;
}
} else {
doLogout(httpRequest, alreadyCalled[0] == 1);
}
}
private void doLogout(HttpRequest request, boolean extensionEnabled) {
Context context = request.getContext();
Authenticator authenticator = null;
if (context != null) {
authenticator = context.getAuthenticator();
}
if (authenticator == null) {
throw new RuntimeException("Context or Authenticator is null");
}
try {
if (extensionEnabled) {
AuthenticatorProxy proxy = new AuthenticatorProxy(authenticator, null, null);
proxy.logout(request);
} else {
authenticator.logout(request);
}
} catch (Exception ex) {
throw new RuntimeException(ex);
}
logout();
}
@Override
public void logout() {
setSecurityContext(null);
// Sets the security context for Jakarta Authorization
WebSecurityManager webSecurityManager = getWebSecurityManager(false);
if (webSecurityManager != null) {
webSecurityManager.onLogout();
}
}
/*
* IASRI 4688449 This method was only used by EEInstanceListener to set the security context prior to invocations by
* re-authenticating a previously set WebPrincipal. This is now cached so no need.
*/
public boolean authenticate(HttpServletRequest request, WebPrincipal principal) {
if (principal.isUsingCertificate()) {
return authenticate(request, null, null, null, principal.getCertificates());
}
return authenticate(request, principal.getName(), principal.getPassword(), null, null);
}
/**
* Authenticates and sets the SecurityContext in the TLS.
*
* @return true if authentication succeeded, false otherwise.
* @param the username.
* @param the authentication method.
* @param the authentication data.
*/
private boolean authenticate(HttpServletRequest request, String username, char[] password, DigestCredentials digestCredentials, X509Certificate[] certificates) {
try {
if (certificates != null) {
LoginContextDriver.doX500Login(generateX500Subject(certificates), moduleID);
} else if (digestCredentials != null) {
LoginContextDriver.login(digestCredentials);
} else {
LoginContextDriver.login(username, password, realmName);
}
_logger.log(FINE, () -> "Web login succeeded for: " + SecurityContext.getCurrent().getCallerPrincipal());
WebSecurityManager webSecurityManager = getWebSecurityManager(false);
// Sets the security context for Jakarta Authorization
if (webSecurityManager != null) {
webSecurityManager.onLogin(request);
}
return true;
} catch (Exception le) {
_logger.log(WARNING, "WEB9102: Web Login Failed", le);
return false;
}
}
/**
* Set the run-as principal into the SecurityContext when needed.
*
*
* This method will attempt to obtain the name of the servlet from the ComponentInvocation. Note that there may not be
* one since this gets called also during internal processing (not clear..) not just part of servlet requests. However,
* if it is not a servlet request there is no need (or possibility) to have a run-as setting so no further action is
* taken.
*
*
* If the servlet name is present the runAsPrincipals cache is checked to find the run-as principal to use (if any). If
* one is set, the SecurityContext is switched to this principal.
*
* @param componentInvocation The invocation object to process.
*
*/
public void preSetRunAsIdentity(ComponentInvocation componentInvocation) {
// Optimization to avoid the expensive call to getServletName
// for cases with no run-as descriptors
if (isEmpty(runAsPrincipals)) {
return;
}
String servletName = getServletName(componentInvocation);
if (servletName == null) {
return;
}
String runAs = runAsPrincipals.get(servletName);
if (runAs != null) {
// The existing SecurityContext is saved - however, this seems
// meaningless - see bug 4757733. For now, keep it unchanged
// in case there are some dependencies elsewhere in RI.
componentInvocation.setOldSecurityContext(getSecurityContext());
// Set the run-as principal into SecurityContext
loginForRunAs(runAs);
_logger.log(FINE, () -> "run-as principal for " + servletName + " set to: " + runAs);
}
}
/**
* Obtain servlet name from invocation.
*
*
* In order to obtain the servlet name one of the following must be true: 1. The instanceName of the ComponentInvocation
* is not null 2. The ComponentInvocation contains a 'class' of type HttpServlet, which contains a valid ServletConfig
* object. This method returns the value returned by getServletName() on the ServletConfig.
*
*
* If the above is not met, null is returned.
*
* @param componentInvocation The invocation object to process.
* @return Servlet name or null.
*
*/
private String getServletName(ComponentInvocation componentInvocation) {
String servletName = componentInvocation.getInstanceName();
if (servletName != null) {
return servletName;
}
Object invocationInstance = componentInvocation.getInstance();
if (invocationInstance instanceof HttpServlet) {
HttpServlet thisServlet = (HttpServlet) invocationInstance;
ServletConfig servletConfig = thisServlet.getServletConfig();
if (servletConfig != null) {
return thisServlet.getServletName();
}
}
return null;
}
/**
* Attempts to restore old SecurityContext (but fails).
*
*
* In theory this method seems to attempt to check if a run-as principal was set by preSetRunAsIdentity() (based on the
* indirect assumption that if the servlet in the given invocation has a run-as this must've been the case). If so, it
* retrieves the oldSecurityContext from the invocation object and set it in the SecurityContext.
*
*
* The problem is that the invocation object is not the same object as was passed in to preSetRunAsIdentity() so it will
* never contain the right info - see bug 4757733.
*
*
* In practice it means this method only ever sets the SecurityContext to null (if run-as matched) or does nothing. In
* particular note the implication that it will be set to null after a run-as invocation completes. This behavior
* will be retained for the time being for consistency with RI. It must be fixed later.
*
* @param inv The invocation object to process.
*
*/
public void postSetRunAsIdentity(ComponentInvocation inv) {
// Optimization to avoid the expensivce call to getServletName
// for cases with no run-as descriptors
if (runAsPrincipals != null && runAsPrincipals.isEmpty()) {
return;
}
String servletName = this.getServletName(inv);
if (servletName == null) {
return;
}
String runAs = runAsPrincipals.get(servletName);
if (runAs != null) {
setSecurityContext((SecurityContext) inv.getOldSecurityContext()); // always null
}
}
// END IASRI 4747594
private void loginForRunAs(String principal) {
LoginContextDriver.loginPrincipal(principal, realmName);
}
private SecurityContext getSecurityContext() {
return SecurityContext.getCurrent();
}
private void setSecurityContext(SecurityContext sc) {
SecurityContext.setCurrent(sc);
}
@Override
protected char[] getPassword(String username) {
throw new IllegalStateException("Should not reach here");
}
@Override
protected Principal getPrincipal(String username) {
throw new IllegalStateException("Should not reach here");
}
// START OF IASRI 4809144
/**
* This method is added to create a Principal based on the username only. Hercules stores the username as part of
* authentication failover and needs to create a Principal based on username only
*
* @param username
* @return Principal for the user username HERCULES:add
*/
public Principal createFailOveredPrincipal(String username) {
_logger.log(FINEST, "IN createFailOveredPrincipal ({0})", username);
loginForRunAs(username);
// set the appropriate security context
SecurityContext securityContext = SecurityContext.getCurrent();
_logger.log(FINE, "Security context is {0}", securityContext);
Principal principal = new WebPrincipal(username, (char[]) null, securityContext);
_logger.log(INFO, "Principal created for FailOvered user {0}", principal);
return principal;
}
/**
* Invokes WebSecurityManager to perform access control check. Return true
if permission is granted, or
* false
otherwise.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraints Security constraint we are enforcing
*
* @exception IOException if an input/output error occurs
*/
private boolean invokeWebSecurityManager(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints) throws IOException {
if (isRequestFormPage(request)) {
return true;
}
setServletPath(request);
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
_logger.log(FINE, () -> "[Web-Security] [ hasResourcePermission ]" +
" Principal: " + httpServletRequest.getUserPrincipal() +
" ContextPath: " + httpServletRequest.getContextPath());
WebSecurityManager webSecurityManager = getWebSecurityManager(true);
if (webSecurityManager == null) {
return false;
}
return webSecurityManager.hasResourcePermission(httpServletRequest);
}
private boolean isRequestFormPage(HttpRequest request) {
initFormPages();
if (isAllNull(loginPage, errorPage)) {
return false;
}
String requestURI = request.getRequestPathMB().toString();
_logger.log(FINE, () -> "[Web-Security] requestURI: " + requestURI + " loginPage: " + loginPage);
if (loginPage != null && loginPage.equals(requestURI)) {
_logger.log(FINE, () -> " Allow access to login page " + loginPage);
return true;
}
if (errorPage != null && errorPage.equals(requestURI)) {
_logger.log(FINE, () -> " Allow access to error page " + errorPage);
return true;
}
if (requestURI.endsWith(Constants.FORM_ACTION)) {
_logger.fine(" Allow access to username/password submission");
return true;
}
return false;
}
private void initFormPages() {
// allow access to form login related pages and targets
// and the "j_security_check" action
boolean evaluated = false;
try {
rwLock.readLock().lock();
evaluated = contextEvaluated;
} finally {
rwLock.readLock().unlock();
}
if (!evaluated) {
try {
rwLock.writeLock().lock();
if (!contextEvaluated) {
// get Context here as preAuthenticateCheck does not have it
// and our Container is always a Context
Context context = (Context) getContainer();
LoginConfig config = context.getLoginConfig();
if (config != null && FORM_METHOD.equals(config.getAuthMethod())) {
loginPage = config.getLoginPage();
errorPage = config.getErrorPage();
}
contextEvaluated = true;
}
} finally {
rwLock.writeLock().unlock();
}
}
}
private void setServletPath(HttpRequest request) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if (httpServletRequest.getServletPath() == null) {
request.setServletPath(getResourceName(httpServletRequest.getRequestURI(), httpServletRequest.getContextPath()));
}
}
private List getHostAndPort(HttpRequest request) throws IOException {
boolean isWebServerRequest = false;
Enumeration headerNames = ((HttpServletRequest) request.getRequest()).getHeaderNames();
String[] hostPort = null;
boolean isHeaderPresent = false;
while (headerNames.hasMoreElements()) {
String headerName = (String) headerNames.nextElement();
String hostVal;
if (headerName.equalsIgnoreCase("Host")) {
hostVal = ((HttpServletRequest) request.getRequest()).getHeader(headerName);
isHeaderPresent = true;
hostPort = hostVal.split(":");
}
}
if (hostPort == null) {
throw new ProtocolException(resourceBundle.getString(MSG_MISSING_HOST_HEADER));
}
// If the port in the Header is empty (it refers to the default port), which is
// not one of the GlassFish listener ports -> GF is front-ended by a proxy (LB plugin)
boolean isHostPortNullOrEmpty = ((hostPort.length <= 1) || (hostPort[1] == null || hostPort[1].trim().isEmpty()));
if (!isHeaderPresent) {
isWebServerRequest = false;
} else if (isHostPortNullOrEmpty) {
isWebServerRequest = true;
} else {
boolean breakFromLoop = false;
for (NetworkListener nwListener : networkListeners.getNetworkListener()) {
// Loop through the network listeners
String nwAddress = nwListener.getAddress();
InetAddress[] localHostAdresses;
if (nwAddress == null || nwAddress.equals("0.0.0.0")) {
nwAddress = NetUtils.getCanonicalHostName();
if (!nwAddress.equals(hostPort[0])) {
// compare the InetAddress objects
// only if the hostname in the header
// does not match with the hostname in the
// listener-To avoid performance overhead
localHostAdresses = NetUtils.getHostAddresses();
InetAddress hostAddress = InetAddress.getByName(hostPort[0]);
for (InetAddress inetAdress : localHostAdresses) {
if (inetAdress.equals(hostAddress)) {
// Hostname of the request in the listener and the hostname in the Host header match.
// Check the port
String nwPort = nwListener.getPort();
// If the listener port is different from the port
// in the Host header, then request is received by WS frontend
if (nwPort.equals(hostPort[1])) {
isWebServerRequest = false;
breakFromLoop = true;
break;
}
isWebServerRequest = true;
}
}
} else {
// Host names are the same, compare the ports
String nwPort = nwListener.getPort();
// If the listener port is different from the port
// in the Host header, then request is received by WS frontend
if (!nwPort.equals(hostPort[1])) {
isWebServerRequest = true;
} else {
isWebServerRequest = false;
breakFromLoop = true;
}
}
}
if (breakFromLoop && !isWebServerRequest) {
break;
}
}
}
String serverHost = request.getRequest().getServerName();
int redirectPort = request.getConnector().getRedirectPort();
// If the request is a from a webserver frontend, redirect to the url
// with the webserver frontend host and port
if (isWebServerRequest) {
serverHost = hostPort[0];
if (isHostPortNullOrEmpty) {
// Use the default port
redirectPort = -1;
} else {
redirectPort = Integer.parseInt(hostPort[1]);
}
}
List hostAndPort = new ArrayList<>();
hostAndPort.add(serverHost);
hostAndPort.add(String.valueOf(redirectPort));
return hostAndPort;
}
private boolean redirect(HttpRequest request, HttpResponse response) throws IOException {
// Initialize variables we need to determine the appropriate action
HttpServletRequest hrequest = (HttpServletRequest) request.getRequest();
HttpServletResponse hresponse = (HttpServletResponse) response.getResponse();
int redirectPort = request.getConnector().getRedirectPort();
// Is redirecting disabled?
if (redirectPort <= 0) {
if (_logger.isLoggable(Level.INFO)) {
_logger.fine("[Web-Security] SSL redirect is disabled");
}
hresponse.sendError(SC_FORBIDDEN, URLEncoder.encode(hrequest.getRequestURI(), "UTF-8"));
return (false);
}
String protocol = "https";
StringBuffer file = new StringBuffer(hrequest.getRequestURI());
String requestedSessionId = hrequest.getRequestedSessionId();
if ((requestedSessionId != null) && hrequest.isRequestedSessionIdFromURL()) {
file.append(";" + Globals.SESSION_PARAMETER_NAME + "=");
file.append(requestedSessionId);
}
String queryString = hrequest.getQueryString();
if (queryString != null) {
file.append('?');
file.append(queryString);
}
URL url = null;
List hostAndPort = getHostAndPort(request);
String serverHost = hostAndPort.get(0);
redirectPort = Integer.parseInt((hostAndPort.get(1)));
try {
url = new URL(protocol, serverHost, redirectPort, file.toString());
hresponse.sendRedirect(url.toString());
return (false);
} catch (MalformedURLException e) {
hresponse.sendError(SC_INTERNAL_SERVER_ERROR, URLEncoder.encode(hrequest.getRequestURI(), "UTF-8"));
return (false);
}
}
// START SJSAS 6232464
// pass in HttpServletResponse instead of saving it as instance variable
// private String getCanonicalName(){
private String getCanonicalName(HttpRequest currentRequest) {
return currentRequest.getWrapper().getServletName();
}
private String getResourceName(String uri, String contextPath) {
if (contextPath.length() < uri.length()) {
return uri.substring(contextPath.length());
}
return "";
}
public void setRealmName(String realmName) {
// do nothing since this is done when initializing the Realm.
}
/**
* This must be invoked after virtualServer is set.
* @throws IOException
*/
private BaseAuthenticationService createAuthenticationService(final ServletContext servletContext) throws IOException {
Map properties = new HashMap<>();
String policyContextId = WebSecurityManager.getContextID(webBundleDescriptor);
if (policyContextId != null) {
properties.put(POLICY_CONTEXT, policyContextId);
}
// "authModuleId" (HttpServletSecurityProvider) is a GlassFish proprietary mechanism where a
// Jakarta Authentication module gets assigned an ID in the proprietary config of GlassFish (domain.xml).
// This ID is then used in glassfish-web.xml to indicate that a war wants to use that authentication module.
String authModuleId =
AuthMessagePolicy.getProviderID(
AuthMessagePolicy.getSunWebApp(Map.of(
WEB_BUNDLE, webBundleDescriptor)));
if (authModuleId != null) {
properties.put("authModuleId", authModuleId);
}
String appContextId = getAppContextID(servletContext);
return new DefaultAuthenticationService(
appContextId,
properties,
new ConfigDomainParser(),
new ServerContainerCallbackHandler(realmName));
}
/**
* This must be invoked after virtualServer is set.
*/
private String getAppContextID(final ServletContext servletContext) {
if (!servletContext.getVirtualServerName().equals(this.virtualServer.getName())) {
_logger.log(WARNING, "Virtual server name from ServletContext: {0} differs from name from virtual.getName(): {1}",
new Object[] { servletContext.getVirtualServerName(), virtualServer.getName() });
}
if (!servletContext.getContextPath().equals(webBundleDescriptor.getContextRoot())) {
_logger.log(WARNING, "Context path from ServletContext: {0} differs from path from bundle: {1}",
new Object[] { servletContext.getContextPath(), webBundleDescriptor.getContextRoot() });
}
return servletContext.getVirtualServerName() + " " + servletContext.getContextPath();
}
private boolean validate(HttpRequest request, HttpResponse response, LoginConfig config, Authenticator authenticator, boolean calledFromAuthenticate) throws IOException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request.getRequest();
HttpServletResponse httpServletResponse = (HttpServletResponse) response.getResponse();
Subject subject = new Subject();
MessageInfo messageInfo = new HttpMessageInfo(httpServletRequest, httpServletResponse);
boolean isRequestValidated = false;
boolean isMandatory = true;
try {
WebSecurityManager webSecurityManager = getWebSecurityManager(true);
isMandatory = !webSecurityManager.permitAll(httpServletRequest);
// Issue - 9578 - produce user challenge if call originates from HttpRequest.authenticate
if (isMandatory || calledFromAuthenticate) {
messageInfo.getMap().put(HttpServletConstants.IS_MANDATORY, Boolean.TRUE.toString());
}
// TODO: call validateRequest
ServerAuthContext serverAuthContext = authenticationService.getServerAuthContext(messageInfo, null); // null serviceSubject
if (serverAuthContext == null) {
throw new AuthException("null ServerAuthContext");
}
AuthStatus authStatus = serverAuthContext.validateRequest(messageInfo, subject, null); // null serviceSubject
isRequestValidated = AuthStatus.SUCCESS.equals(authStatus);
if (isRequestValidated) { // cache it only if validateRequest = true
messageInfo.getMap().put(SERVER_AUTH_CONTEXT, serverAuthContext);
httpServletRequest.setAttribute(MESSAGE_INFO, messageInfo);
}
} catch (AuthException ae) {
_logger.log(SEVERE, "Jakarta Authentication: http msg authentication fail", ae);
httpServletResponse.setStatus(SC_INTERNAL_SERVER_ERROR);
} catch (RuntimeException e) {
_logger.log(SEVERE , "Jakarta Authentication: Exception during validateRequest", e);
httpServletResponse.sendError(SC_INTERNAL_SERVER_ERROR);
}
if (isRequestValidated) {
Caller caller = getCaller(subject);
// Must have a caller to establish non-default security context
if (caller != null) {
// Convert Epicyro representation of the Caller Principal / Groups to the existing
// GlassFish one. A future version of this code may use the Epicyro one everywhere directly.
subject = new Subject();
// See if there's a Subject stored in the session that contain all relevant principals and
// credentials for reuse, and the caller has indicated to take these.
Subject sessionSubject = reuseSessionSubject(caller);
if (sessionSubject != null) {
// Copy principals, public credentials and private credentials from the Subject that lives in
// the session to the receiving Subject.
copySubject(subject, sessionSubject);
} else {
Principal glassFishCallerPrincipal = getGlassFishCallerPrincipal(caller);
toSubject(subject, glassFishCallerPrincipal);
toSubjectCredential(subject, new DistinguishedPrincipalCredential(glassFishCallerPrincipal));
for (String group : caller.getGroups()) {
toSubject(subject, new Group(group));
}
if (!glassFishCallerPrincipal.equals(SecurityContext.getDefaultCallerPrincipal())) {
// Give native GlassFish (realms, mostly) opportunity to add groups
LoginContextDriver.jmacLogin(subject, glassFishCallerPrincipal, realmName);
SecurityContext ctx = new SecurityContext(subject);
SecurityContext.setCurrent(ctx);
// XXX assuming no null principal here
Principal principal = ctx.getCallerPrincipal();
WebPrincipal webPrincipal = new WebPrincipal(principal, ctx);
try {
String authType = (String) messageInfo.getMap().get(HttpServletConstants.AUTH_TYPE);
if (authType == null && config != null && config.getAuthMethod() != null) {
authType = config.getAuthMethod();
}
if (shouldRegister(messageInfo.getMap())) {
// Sets webPrincipal for the session and request
new AuthenticatorProxy(authenticator, webPrincipal, authType)
.authenticate(request, response, config);
} else {
// Sets webPrincipal for the request only
request.setAuthType(authType == null ? PROXY_AUTH_TYPE : authType);
request.setUserPrincipal(webPrincipal);
}
} catch (LifecycleException le) {
_logger.log(SEVERE, "[Web-Security] unable to register session", le);
}
} else {
// GLASSFISH-20930.Set null for the case when SAM does not
// indicate that it needs the session
if (((HttpServletRequest) messageInfo.getRequestMessage()).getUserPrincipal() != null) {
request.setUserPrincipal(null);
request.setAuthType(null);
}
if (isMandatory) {
isRequestValidated = false;
}
}
}
}
if (isRequestValidated) {
HttpServletRequest newRequest = (HttpServletRequest) messageInfo.getRequestMessage();
if (newRequest != httpServletRequest) {
request.setNote(WRAPPED_REQUEST, new HttpRequestWrapper(request, newRequest));
}
HttpServletResponse newResponse = (HttpServletResponse) messageInfo.getResponseMessage();
if (newResponse != httpServletResponse) {
request.setNote(WRAPPED_RESPONSE, new HttpResponseWrapper(response, newResponse));
}
}
}
return isRequestValidated;
}
private Caller getCaller(Subject subject) {
Set callers = subject.getPrincipals(Caller.class);
if (callers.isEmpty()) {
return null;
}
return callers.iterator().next();
}
/**
* See if we need to wrap back the principal.
*
*
* This situation occurs when according to the Jakarta Authentication "special move" the Principal
* from the request is passed into the callback handler. This signals that a SAM wants to re-use
* a previously saved authenticated identity.
*
*
* However, in GlassFish getting a Principal from the request will automatically unwrap it if a
* custom principal was used. Here we try to find the original wrapping principal, if any.
*
* @param principal
* @return
*/
private Principal findPrincipalWrapper(Principal principal) {
if (principal != null && !(principal instanceof WebPrincipal)) {
// Get the top level session principal
Principal sessionPrincipal = SecurityContext.getCurrent().getSessionPrincipal();
// If it's the wrapper we're looking for, it must be of type WebPrincipal
if (sessionPrincipal instanceof WebPrincipal) {
WebPrincipal webPrincipalFromSession = (WebPrincipal) sessionPrincipal;
// Check if the top level session principal is indeed wrapping our current principal
if (webPrincipalFromSession.getCustomPrincipal() == principal) {
// Custom principal from wrapper is the same as our current principal, so
// this is the wrapper we're looking for.
return webPrincipalFromSession;
}
}
}
// Not wrapped, or wrapper could not be found
return principal;
}
private Subject reuseSessionSubject(final Caller caller) {
Principal returnedPrincipal = findPrincipalWrapper(caller.getCallerPrincipal());
if (returnedPrincipal instanceof WebPrincipal) {
return reuseWebPrincipal((WebPrincipal) returnedPrincipal);
}
return null;
}
/**
* This method will distinguish the initiator principal (of the SecurityContext obtained from the WebPrincipal) as the
* caller principal, and copy all the other principals into the subject....
*
* It is assumed that the input WebPrincipal is coming from a SAM, and that it was created either by the SAM (as
* described below) or by calls to the LoginContextDriver made by an Authenticator.
*
* A WebPrincipal constructed by the RealmAdapter will include a DistinguishedPrincipalCredential; other constructions may not; this method
* interprets the absence of a DPC as evidence that the resulting WebPrincipal was not constructed by the RealmAdapter
* as described below. Note that presence of a DistinguishedPrincipalCredential does not necessarily mean that the resulting WebPrincipal was
* constructed by the RealmAdapter... since some authenticators also add the credential).
*
* A. handling of CPCB by CBH:
*
* 1. handling of CPC by CBH modifies subject a. constructs principalImpl if called by name b. uses LoginContextDriver
* to add group principals for name c. puts principal in principal set, and DPC in public credentials
*
* B. construction of WebPrincipal by RealmAdapter (occurs after SAM uses CBH to set other than an unauthenticated
* result in the subject:
*
* a. SecurityContext construction done with subject (returned by SAM). Construction sets initiator/caller principal
* within SC from DistinguishedPrincipalCredential set by CBH in public credentials of subject
*
* b WebPrincipal is constructed with initiator principal and SecurityContext
*
* @param webPrincipal WebPrincipal
*
* @return true when Security Context has been obtained from webPrincipal, and CB is finished. returns false when more
* CB processing is required.
*/
private Subject reuseWebPrincipal(final WebPrincipal webPrincipal) {
SecurityContext securityContext = webPrincipal.getSecurityContext();
final Subject securityContextSubject = securityContext != null ? securityContext.getSubject() : null;
final Principal callerPrincipal = securityContext != null ? securityContext.getCallerPrincipal() : null;
final Principal defaultPrincipal = SecurityContext.getDefaultCallerPrincipal();
// This method uses 4 (numbered) criteria to determine if the argument WebPrincipal can be reused
/**
* 1. WebPrincipal must contain a SecurityContext and SC must have a non-null, non-default callerPrincipal and a Subject
*/
if (callerPrincipal == null || callerPrincipal.equals(defaultPrincipal) || securityContextSubject == null) {
return null;
}
boolean hasObject = false;
Set distinguishedCreds = securityContextSubject.getPublicCredentials(DistinguishedPrincipalCredential.class);
if (distinguishedCreds.size() == 1) {
for (DistinguishedPrincipalCredential cred : distinguishedCreds) {
if (cred.getPrincipal().equals(callerPrincipal)) {
hasObject = true;
}
}
}
/**
* 2. Subject within SecurityContext must contain a single DistinguishedPrincipalCredential that identifies the Caller Principal
*/
if (!hasObject) {
return null;
}
hasObject = securityContextSubject.getPrincipals().contains(callerPrincipal);
/**
* 3. Subject within SecurityContext must contain the caller principal
*/
if (!hasObject) {
return null;
}
/**
* 4. The webPrincipal must have a non null name that equals the name of the callerPrincipal.
*/
if (webPrincipal.getName() == null || !webPrincipal.getName().equals(callerPrincipal.getName())) {
return null;
}
return securityContextSubject;
}
private Principal getGlassFishCallerPrincipal(Caller caller) {
Principal callerPrincipal = caller.getCallerPrincipal();
// Check custom principal
if (callerPrincipal instanceof CallerPrincipal == false) {
return callerPrincipal;
}
// Check anonymous principal
if (callerPrincipal.getName() == null) {
return SecurityContext.getDefaultCallerPrincipal();
}
// Check certificate / X500 principal (this is oddly specific)
if (CertificateRealm.AUTH_TYPE.equals(realmName)) {
return new X500Principal(callerPrincipal.getName());
}
return new UserNameAndPassword(callerPrincipal.getName());
}
public static void copySubject(Subject target, Subject source) {
target.getPrincipals().addAll(source.getPrincipals());
target.getPublicCredentials().addAll(source.getPublicCredentials());
target.getPrivateCredentials().addAll(source.getPrivateCredentials());
}
public static void toSubject(Subject subject, Principal principal) {
subject.getPrincipals().add(principal);
}
public static void toSubject(Subject subject, Set principals) {
subject.getPrincipals().addAll(principals);
}
public static void toSubjectCredential(Subject subject, Object credential) {
subject.getPublicCredentials().add(credential);
}
public static void removeFromCredentials(Subject subject, Class> typeToRemove) {
Iterator