/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can obtain
* a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
* or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
* Sun designates this particular file as subject to the "Classpath" exception
* as provided by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own
* identifying information: "Portions Copyrighted [year]
* [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
package com.sun.web.security;
import com.sun.enterprise.security.auth.digest.impl.HttpAlgorithmParameterImpl;
import com.sun.enterprise.security.auth.digest.impl.HttpDigestParamGenerator;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.security.auth.Subject;
import javax.security.auth.message.AuthException;
import javax.security.auth.message.AuthStatus;
import javax.security.auth.message.MessageInfo;
import javax.security.auth.message.config.AuthConfigFactory;
import javax.security.auth.message.config.AuthConfigProvider;
import javax.security.auth.message.config.ServerAuthConfig;
import javax.security.auth.message.config.ServerAuthContext;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequestWrapper;
import sun.security.x509.X500Name;
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.Realm;
import org.apache.catalina.authenticator.AuthenticatorBase;
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.apache.catalina.util.StringManager;
import com.sun.enterprise.ComponentInvocation;
import com.sun.enterprise.Switch;
import com.sun.enterprise.admin.monitor.callflow.Agent;
import com.sun.enterprise.admin.monitor.callflow.RequestInfo;
import com.sun.enterprise.admin.monitor.callflow.ContainerTypeOrApplicationType;
import com.sun.enterprise.deployment.Application;
import com.sun.enterprise.deployment.RunAsIdentityDescriptor;
import com.sun.enterprise.deployment.WebBundleDescriptor;
import com.sun.enterprise.deployment.WebComponentDescriptor;
import com.sun.enterprise.deployment.interfaces.SecurityRoleMapper;
import com.sun.enterprise.deployment.web.LoginConfiguration;
import com.sun.enterprise.security.SecurityContext;
import com.sun.enterprise.security.auth.LoginContextDriver;
import com.sun.enterprise.security.auth.realm.certificate.CertificateRealm;
import com.sun.enterprise.security.jmac.config.HttpServletConstants;
import com.sun.enterprise.security.jmac.config.HttpServletHelper;
import com.sun.enterprise.webservice.monitoring.WebServiceEngineImpl;
import com.sun.enterprise.webservice.monitoring.AuthenticationListener;
import com.sun.logging.LogDomains;
import com.sun.enterprise.security.auth.digest.api.DigestAlgorithmParameter;
import com.sun.enterprise.security.auth.login.DigestCredentials;
import com.sun.enterprise.security.auth.digest.api.Key;
import com.sun.enterprise.security.auth.digest.api.DigestParameterGenerator;
import static com.sun.enterprise.security.auth.digest.api.Constants.A1;
/**
* 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
*/
public class RealmAdapter extends RealmBase {
private static final String UNCONSTRAINED = "unconstrained";
private static final Logger _logger=LogDomains.getLogger(LogDomains.WEB_LOGGER);
public static final String SECURITY_CONTEXT = "SecurityContext";
public static final String BASIC = "BASIC";
public static final String FORM = "FORM";
private static final String SERVER_AUTH_CONTEXT = "__javax.security.auth.message.ServerAuthContext";
private static final String MESSAGE_INFO = "__javax.security.auth.message.MessageInfo";
// 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 String realm = "default";
private SecurityRoleMapper mapper = null;
private WebBundleDescriptor webDesc = null;
// BEGIN IASRI 4747594
private HashMap runAsPrincipals = null;
// END IASRI 4747594
// required for realm-per-app login
private String _realmName = null;
/**
* Descriptive information about this Realm implementation.
*/
protected static final String name = "J2EE-RI-RealmAdapter";
/**
* The context Id value needed by the jacc architecture.
*/
private String CONTEXT_ID = null;
private Container virtualServer;
/**
* The string manager for this package.
*/
protected static final StringManager sm =
StringManager.getManager(Constants.Package);
protected static final StringManager smRA =
StringManager.getManager("com.sun.web.security");
/**
* A <code>WebSecurityManager</code> object associated with a CONTEXT_ID
*/
protected volatile WebSecurityManager webSecurityManager = null;
protected boolean isCurrentURIincluded = false;
private ArrayList roles = null;
/* 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 = null;
private String errorPage = null;
private 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 appID;
private boolean isSystemApp;
private String jmacProviderRegisID = null;
private HttpServletHelper helper = null;
public RealmAdapter() {}
/**
* Create for WS Ejb endpoint authentication.
* Roles related data is not available here.
*/
public RealmAdapter(String realmName) {
_realmName = realmName;
}
/**
* Create the realm adapter. Extracts the role to user/group mapping
* from the runtime deployment descriptor.
* @param the web bundle deployment descriptor.
* @param isSystemApp if the app is a system app.
*/
public RealmAdapter(WebBundleDescriptor descriptor, boolean isSystemApp) {
this(descriptor, isSystemApp, null);
}
/**
* Create the realm adapter. Extracts the role to user/group mapping
* from the runtime deployment descriptor.
* @param the web bundle deployment descriptor.
* @param isSystemApp if the app is a system app.
* @param realmName The realm name to use if the app does not specify its
* own
*/
public RealmAdapter(WebBundleDescriptor descriptor,
boolean isSystemApp,
String realmName) {
this.isSystemApp = isSystemApp;
webDesc = descriptor;
Application app = descriptor.getApplication();
mapper = app.getRoleMapper();
LoginConfiguration loginConfig = descriptor.getLoginConfiguration();
_realmName = app.getRealm();
if (_realmName == null && loginConfig != null) {
_realmName = loginConfig.getRealmName();
}
if (realmName != null
&& (_realmName == null || _realmName.equals(""))) {
_realmName = realmName;
}
// BEGIN IASRI 4747594
CONTEXT_ID = WebSecurityManager.getContextID(descriptor);
runAsPrincipals = new HashMap();
Iterator bundle = webDesc.getWebComponentDescriptorsSet().iterator();
while(bundle.hasNext()) {
WebComponentDescriptor wcd = (WebComponentDescriptor)bundle.next();
RunAsIdentityDescriptor runAsDescriptor = wcd.getRunAsIdentity();
if (runAsDescriptor != null) {
String principal = runAsDescriptor.getPrincipal();
String servlet = wcd.getCanonicalName();
if (principal == null || servlet == null) {
_logger.warning("web.realmadapter.norunas");
} else {
runAsPrincipals.put(servlet, principal);
_logger.fine("Servlet "+servlet+
" will run-as: "+principal);
}
}
}
// END IASRI 4747594
this.appID = app.getRegistrationName();
// helper are set until setVirtualServer is invoked
}
protected void addRunAsPrincipal(String servletName,String principal){
runAsPrincipals.put(servletName, principal);
}
public void destroy() {
super.destroy();
if (helper != null) {
helper.disable();
}
}
/**
* Sets the virtual server on which the web module (with which this
* RealmAdapter is associated with) has been deployed.
*
* @param container The virtual server
*/
public void setVirtualServer(Container container) {
this.virtualServer = container;
this.helper = getConfigHelper();
}
public WebBundleDescriptor getWebDescriptor() {
return webDesc;
}
// utility method to get web security anager.
// will log warning if the manager is not found in the factory, and
// logNull is true.
WebSecurityManager getWebSecurityManager(boolean logNull) {
if (webSecurityManager == null) {
synchronized(this) {
webSecurityManager = WebSecurityManager.getManager(CONTEXT_ID,null,false);
}
if (webSecurityManager == null && logNull) {
String msg = smRA.getString("realmAdapter.noWebSecMgr", CONTEXT_ID);
_logger.warning(msg);
}
}
return webSecurityManager;
}
/**
* 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
*/
//START OF SJSAS 6232464
//public boolean hasRole(Principal principal, String role) {
public boolean hasRole(HttpRequest request,
HttpResponse response,
Principal principal,
String role) {
WebSecurityManager secMgr = getWebSecurityManager(true);
if (secMgr == 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());
HttpServletRequest hrequest = (HttpServletRequest) request;
String servletName = getResourceName( hrequest.getRequestURI(),
hrequest.getContextPath());
//END OF SJSAS 6232464
// First try with the request.
boolean isGranted =
secMgr.hasRoleRefPermission(servletName, role, principal);
if (!isGranted){
// START S1AS8PE 4966609
// This case occurs when a direct call is made
// to a jsp instead of using the server-name element defined in web.xml
servletName = getCanonicalName(hrequest);
// If we can't find any servletMapping for a resource
// (usually a jsp), return false.
if (servletName.equalsIgnoreCase(UNCONSTRAINED)){
if(_logger.isLoggable(Level.INFO)){
_logger.log(Level.INFO,
"Unable to find a <servlet-name> element which map: "
+ hrequest.getRequestURI());
}
/*
* For every security role in the web application add a
* WebRoleRefPermission to the corresponding role. The name of all such
* permissions shall be the empty string, and the actions of each
* permission shall be the corresponding role name. When checking a
* WebRoleRefPermission from a JSP not mapped to a servlet, use a
* permission with the empty string as its name
* and with the argument to isUserInRole as its actions
*/
isGranted = secMgr.hasRoleRefPermission("", role, principal);
// END S1AS8PE 4966609
} else {
isGranted = secMgr.hasRoleRefPermission(servletName,
role,
principal);
}
}
if(_logger.isLoggable(Level.FINE)) {
_logger.fine( "Checking if servlet " + servletName
+ " with principal " + principal
+ " has role " + role
+ " isGranted: " + isGranted);
}
return isGranted;
}
public boolean hasRole(String servletName, Principal principal, String role) {
WebSecurityManager secMgr = getWebSecurityManager(true);
if (secMgr == null) {
return false;
}
return secMgr.hasRoleRefPermission(servletName, role, principal);
}
public void logout() {
setSecurityContext(null);
}
public Principal authenticate(HttpServletRequest hreq) {
try {
DigestParameterGenerator generator = DigestParameterGenerator.getInstance(DigestParameterGenerator.HTTP_DIGEST);
DigestAlgorithmParameter[] params = generator.generateParameters(new HttpAlgorithmParameterImpl(hreq));
Key key = null;
for(int i=0;i<params.length;i++){
DigestAlgorithmParameter dap = params[i];
if(A1.equals(dap.getName()) && (dap instanceof Key)){
key = (Key)dap;
break;
}
}
DigestCredentials creds = new DigestCredentials(_realmName,key.getUsername(), params);
LoginContextDriver.login(creds);
SecurityContext secCtx = SecurityContext.getCurrent();
return new WebPrincipal(creds.getUserName(), null, secCtx);
} catch (Exception le) {
if (_logger.isLoggable(Level.WARNING)) {
_logger.warning("Web login failed: " + le.getMessage());
}
}
return null;
}
/**
* Authenticates and sets the SecurityContext in the TLS.
* @return the authenticated principal.
* @param the user name.
* @param the authenticated data.
*/
public Principal authenticate(String username, byte[] authData) {
return authenticate(username, new String(authData));
}
/**
* Authenticates and sets the SecurityContext in the TLS.
* @return the authenticated principal.
* @param the user name.
* @param the password.
*/
public Principal authenticate(String username, String password) {
if (_logger.isLoggable(Level.FINE)){
_logger.fine( "Tomcat callback for authenticate user/password");
_logger.fine( "usename = " + username);
}
if(authenticate(username, password, null)) {
SecurityContext secCtx = SecurityContext.getCurrent();
assert (secCtx != null); // or auth should've failed
return new WebPrincipal(username, password, secCtx);
} else {
return null;
}
}
public Principal authenticate(X509Certificate certs[]) {
if(authenticate(null, null, certs)) {
SecurityContext secCtx = SecurityContext.getCurrent();
assert (secCtx != null); // or auth should've failed
return new WebPrincipal(certs, secCtx);
} else {
return null;
}
}
/* IASRI 4688449
This method was only used by J2EEInstanceListener 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(WebPrincipal prin) {
if (prin.isUsingCertificate()) {
return authenticate(null, null, prin.getCertificates());
} else {
return authenticate(prin.getName(), prin.getPassword(), 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.
*/
protected boolean authenticate(String username, String password,
X509Certificate[] certs) {
SecurityContext.setCurrent(null);
String realm_name = null;
boolean success = false;
try {
if (certs != null) {
Subject subject = new Subject();
X509Certificate certificate = certs[0];
X500Name x500Name = (X500Name) certificate.getSubjectDN();
Switch.getSwitch().getCallFlowAgent().addRequestInfo(
RequestInfo.REMOTE_USER, x500Name.getName());
subject.getPublicCredentials().add(x500Name);
LoginContextDriver.login(subject, X500Name.class);
realm_name = CertificateRealm.AUTH_TYPE;
} else {
Switch.getSwitch().getCallFlowAgent().addRequestInfo(
RequestInfo.REMOTE_USER, username);
realm_name = _realmName;
LoginContextDriver.login(username, password, realm_name);
}
success = true;
} catch (Exception le) {
success = false;
if (_logger.isLoggable(Level.WARNING)) {
_logger.warning("Web login failed: " + le.getMessage());
}
}
if(success){
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,"Web login succeeded for: " + username);
}
}
if (webDesc!=null && webDesc.hasWebServices()) {
WebPrincipal principal = new WebPrincipal(username, password, null);
for (AuthenticationListener listener : WebServiceEngineImpl.getInstance().getAuthListeners()) {
if (success) {
listener.authSucess(webDesc, null, principal);
} else {
listener.authFailure(webDesc, null, principal);
}
}
}
return success;
}
// BEGIN IASRI 4747594
/**
* Set the run-as principal into the SecurityContext when needed.
*
* <P>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.
*
* <P>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 inv The invocation object to process.
*
*/
public void preSetRunAsIdentity (ComponentInvocation inv) {
String name=this.getServletName(inv);
if (name == null) {
return;
}
String runAs = (String)runAsPrincipals.get(name);
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.
SecurityContext old = getSecurityContext();
inv.setOldSecurityContext(old);
// Set the run-as principal into SecurityContext
loginForRunAs(runAs);
if (_logger.isLoggable(Level.FINE)) {
_logger.fine("run-as principal for " + name +
" set to: "+ runAs);
}
}
}
/**
* Obtain servlet name from invocation.
*
* <P>In order to obtain the servlet name the following must be true:
* 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 inv The invocation object to process.
* @return Servlet name or null.
*
*/
protected String getServletName(ComponentInvocation inv)
{
Object invInstance = inv.getInstance();
if (invInstance instanceof HttpServlet) {
HttpServlet thisServlet = (HttpServlet)invInstance;
ServletConfig svc = thisServlet.getServletConfig();
if (svc != null) {
return thisServlet.getServletName();
}
}
return null;
}
/**
* Attempts to restore old SecurityContext (but fails).
*
* <P>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.
*
* <P>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.
*
* <P>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 <i>will</i> 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){
String name=this.getServletName(inv);
if (name == null) {
return;
}
String runAs = (String)runAsPrincipals.get(name);
if (runAs != null) {
setSecurityContext (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);
}
/**
* Used to detect when the principals in the subject correspond to the
* default or "ANONYMOUS" principal, and therefore a null principal
* should be set in the HttpServletRequest.
* @param principalSet
* @return true whe a null principal is to be set.
*/
private boolean principalSetContainsOnlyAnonymousPrincipal(Set principalSet) {
boolean rvalue = false;
Principal defaultPrincipal = SecurityContext.getDefaultCallerPrincipal();
if (defaultPrincipal != null && principalSet != null) {
rvalue = principalSet.contains(defaultPrincipal);
}
if (rvalue) {
Iterator<Principal> it = principalSet.iterator();
while (it.hasNext()) {
if (!it.next().equals(defaultPrincipal)) {
return false;
}
}
}
return rvalue;
}
protected String getPassword(String username) {
throw new IllegalStateException("Should not reach here");
}
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 <sridhar.satuloori@sun.com>
* @param username
* @return Principal for the user username
* HERCULES:add
*/
public Principal createFailOveredPrincipal(String username){
_logger.log(Level.FINEST,"IN createFailOveredPrincipal ("+username+")");
//set the appropriate security context
loginForRunAs(username);
SecurityContext secCtx = SecurityContext.getCurrent();
_logger.log(Level.FINE,"Security context is "+secCtx);
assert (secCtx != null);
Principal principal = new WebPrincipal(username, null, secCtx);
_logger.log(Level.INFO,"Principal created for FailOvered user "+principal);
return principal;
}
//END OF IASRI 4809144
/**
* Perform access control based on the specified authorization constraint.
* Return <code>true</code> if this constraint is satisfied and processing
* should continue, or <code>false</code> 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
*/
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) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
response.setDetailMessage(sm.getString("realmBase.forbidden"));
return isGranted;
}
if ( isGranted ){
return isGranted;
} else {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_FORBIDDEN);
response.setDetailMessage(sm.getString("realmBase.forbidden"));
// invoking secureResponse
invokePostAuthenticateDelegate(request, response, context);
return isGranted;
}
}
/**
* Invokes WebSecurityManager to perform access control check.
* Return <code>true</code> if permission is granted, or <code>false</code>
* otherwise.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraint Security constraint we are enforcing
*
* @exception IOException if an input/output error occurs
*/
private boolean invokeWebSecurityManager(HttpRequest request,
HttpResponse response,
SecurityConstraint[] constraints)
throws IOException {
// 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) &&
(Constants.FORM_METHOD.equals(config.getAuthMethod()))) {
loginPage = config.getLoginPage();
errorPage = config.getErrorPage();
}
contextEvaluated = true;
}
} finally {
rwLock.writeLock().unlock();
}
}
if (loginPage != null || errorPage != null) {
String requestURI = request.getRequestPathMB().toString();
if(_logger.isLoggable(Level.FINE)) {
_logger.fine("[Web-Security] requestURI: " + requestURI +
" loginPage: " + loginPage);
}
if (loginPage != null && loginPage.equals(requestURI)) {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine(" Allow access to login page " + loginPage);
}
return true;
}
else if (errorPage != null && errorPage.equals(requestURI)) {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine(" Allow access to error page " + errorPage);
}
return true;
}
else if (requestURI.endsWith(Constants.FORM_ACTION)) {
if(_logger.isLoggable(Level.FINE)) {
_logger.fine(" Allow access to username/password submission");
}
return true;
}
}
HttpServletRequest hrequest = (HttpServletRequest)request;
if ( hrequest.getServletPath() == null){
request.setServletPath( getResourceName( hrequest.getRequestURI(),
hrequest.getContextPath()));
}
if(_logger.isLoggable(Level.FINE))
_logger.fine("[Web-Security] [ hasResourcePermission ] Principal: "
+ hrequest.getUserPrincipal() + " ContextPath: " + hrequest.getContextPath());
WebSecurityManager secMgr = getWebSecurityManager(true);
if (secMgr == null) {
return false;
}
return secMgr.hasResourcePermission(hrequest);
}
/**
* Enforce any user data constraint required by the security constraint
* guarding this request URI. Return <code>true</code> if this constraint
* was not violated and processing should continue, or <code>false</code>
* if we have created a response already.
*
* @param request Request we are processing
* @param response Response we are creating
* @param constraint Security constraint being checked
*
* @exception IOException if an input/output error occurs
*/
public boolean hasUserDataPermission(HttpRequest request,
HttpResponse response,
SecurityConstraint[] constraints) throws IOException {
HttpServletRequest hrequest = (HttpServletRequest)request;
if ( hrequest.getServletPath() == null){
request.setServletPath(
getResourceName( hrequest.getRequestURI(),
hrequest.getContextPath()));
}
if(_logger.isLoggable(Level.FINE)){
_logger.fine("[Web-Security][ hasUserDataPermission ] Principal: "
+ hrequest.getUserPrincipal()
+ " ContextPath: "
+ hrequest.getContextPath());
}
if (request.getRequest().isSecure()) {
if(_logger.isLoggable(Level.FINE))
_logger.fine("[Web-Security] request.getRequest().isSecure(): " + request.getRequest().isSecure());
return true;
}
WebSecurityManager secMgr = getWebSecurityManager(true);
if (secMgr == null) {
return false;
}
int isGranted = 0;
try {
isGranted = secMgr.hasUserDataPermission(hrequest);
} catch (IllegalArgumentException e) {
//end the request after getting IllegalArgumentException while checking
//user data permission
String msg = smRA.getString("realmAdapter.badRequest", e.getMessage());
_logger.warning(msg);
if(_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE, msg, e);
}
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_BAD_REQUEST, msg);
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 ){
if(_logger.isLoggable(Level.FINE))
_logger.fine( "[Web-Security] redirecting using SSL");
return redirect(request, response);
}
if (isGranted == 0){
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_FORBIDDEN,
sm.getString("realmBase.forbidden"));
return false;
}
return true;
}
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
(HttpServletResponse.SC_FORBIDDEN, hrequest.getRequestURI());
return (false);
}
String protocol = "https";
String host = hrequest.getServerName();
StringBuffer file = new StringBuffer(hrequest.getRequestURI());
String requestedSessionId = hrequest.getRequestedSessionId();
if ((requestedSessionId != null) &&
hrequest.isRequestedSessionIdFromURL()) {
file.append(";jsessionid=");
file.append(requestedSessionId);
}
String queryString = hrequest.getQueryString();
if (queryString != null) {
file.append('?');
file.append(queryString);
}
URL url = null;
try {
url = new URL(protocol, host, redirectPort, file.toString());
hresponse.sendRedirect(url.toString());
return (false);
} catch (MalformedURLException e) {
hresponse.sendError
(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
hrequest.getRequestURI());
return (false);
}
}
//START SJSAS 6232464
//pass in HttpServletResponse instead of saving it as instance variable
//private String getCanonicalName(){
private String getCanonicalName(HttpServletRequest currentRequest){
//END SJSAS 6232464
String servletUri = "";
String currentUri = "";
String aliasUri = "";
String currentUriExtension = "";
String aliasUriExtension = "";
boolean isAliasExists = false;
for (Iterator itr = webDesc.getWebComponentDescriptorsSet().iterator(); itr.hasNext();) {
WebComponentDescriptor webComponentDescriptor = (WebComponentDescriptor)itr.next();
servletUri = webComponentDescriptor.getWebComponentImplementation();
currentUri = getResourceName( currentRequest.getRequestURI(), currentRequest.getContextPath());
currentUriExtension = getExtension(currentUri);
// First check the servlet mapping
for (Iterator i = webComponentDescriptor.getUrlPatternsSet().iterator(); i.hasNext();) {
aliasUri = i.next().toString();
aliasUriExtension = getExtension(aliasUri);
if (aliasUri.equalsIgnoreCase(currentUri)){
isAliasExists = true;
break;
}
if (aliasUriExtension.equalsIgnoreCase(currentUriExtension)
&& aliasUri.equalsIgnoreCase("*" + aliasUriExtension)){
isAliasExists = true;
break;
}
}
if (currentUri.equalsIgnoreCase(servletUri)
|| isAliasExists){
return webComponentDescriptor.getCanonicalName();
}
}
return UNCONSTRAINED;
}
private String getResourceName(String uri, String contextPath){
try{
return uri.substring(contextPath.length());
} catch (java.lang.Exception ex){
return "";
}
}
private String getExtension(String uri){
try{
return uri.substring(uri.lastIndexOf("."));
} catch (java.lang.Exception ex){
// don't use the cache and let jacc create the permission.
return "";
}
}
/**
* Return a short name for this Realm Adapter implementation.
*/
protected String getName() {
return (this.name);
}
/**
* Return the name of the realm this RealmAdapter uses.
*
* @return realm name
*
*/
public String getRealmName() {
return _realmName;
}
public void setRealmName(String realmName){
// do nothing since this is done when initializing the Realm.
}
//START SJSAS 6232464 6202703
/**
* Returns null
* 1. if there are no security constrainst 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.
*/
public SecurityConstraint[] findSecurityConstraints(HttpRequest request,
Context context) {
WebSecurityManager secMgr = getWebSecurityManager(false);
if (secMgr != null && secMgr.hasNoConstrainedResources()) {
return null;
}
SecurityConstraint[] constraints = RealmAdapter.emptyConstraints;
return constraints;
}
//END SJSAS 6232464 6202703
//START SJSAS 6202703
/**
* Checks whether or not authentication is 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
*/
public int preAuthenticateCheck(HttpRequest request,
HttpResponse response,
SecurityConstraint[] constraints,
boolean disableProxyCaching,
boolean securePagesWithPragma,
boolean ssoEnabled)
throws IOException {
boolean isGranted = false;
try {
if (helper != null && helper.getServerAuthConfig() != null) {
return Realm.AUTHENTICATE_NEEDED;
}
isGranted = invokeWebSecurityManager(
request, response, constraints);
} catch(IOException iex) {
throw iex;
} catch(Throwable ex) {
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
response.setDetailMessage(sm.getString("realmBase.forbidden"));
return Realm.AUTHENTICATED_NOT_AUTHORIZED;
}
if(isGranted) {
HashMap sharedState = null;
boolean delegateSessionMgmt = false;
//XXX Keep it for reference
/*
if (this.sAC != null) {
sharedState = new HashMap();
try {
delegateSessionMgmt = this.sAC.managesSessions(sharedState);
} catch (AuthException ae) {
delegateSessionMgmt = false;
}
}
if (delegateSessionMgmt) {
if (validate(request, response, null, null)) {
disableProxyCaching(request, response, disableProxyCaching,
securePagesWithPragma);
}
} else if( ((HttpServletRequest) request).getUserPrincipal() != null) {
*/
if( ((HttpServletRequest) request).getUserPrincipal() != null) {
disableProxyCaching(request, response, disableProxyCaching,
securePagesWithPragma);
if (ssoEnabled) {
HttpServletRequest hreq =
(HttpServletRequest)request.getRequest();
WebSecurityManager webSecMgr = getWebSecurityManager(true);
if (!webSecMgr.permitAll(hreq)) {
//create a session for protected sso association
hreq.getSession(true);
}
}
}
return Realm.AUTHENTICATE_NOT_NEEDED;
} else if(((HttpServletRequest) request).getUserPrincipal() != null){
((HttpServletResponse) response.getResponse()).sendError
(HttpServletResponse.SC_FORBIDDEN);
response.setDetailMessage(sm.getString("realmBase.forbidden"));
return Realm.AUTHENTICATED_NOT_AUTHORIZED;
} else {
disableProxyCaching(request, response, disableProxyCaching,securePagesWithPragma);
return Realm.AUTHENTICATE_NEEDED;
}
}
/**
* Authenticates the user making this request, based on the specified
* login configuration. Return <code>true</code> if any specified
* requirements have been satisfied, or <code>false</code> 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
*/
public boolean invokeAuthenticateDelegate(HttpRequest request,
HttpResponse response,
Context context,
Authenticator authenticator)
throws IOException {
boolean result = false;
LoginConfig config = context.getLoginConfig();
ServerAuthConfig serverAuthConfig = null;
try {
if (helper != null) {
serverAuthConfig = helper.getServerAuthConfig();
}
} catch(Exception ex) {
IOException iex = new IOException();
iex.initCause(ex);
throw iex;
}
if (serverAuthConfig != null) {
//JSR 196 is enabled for this application
result = validate(request,response, config, authenticator);
} else {
//jsr196 is not enabled. Use the current authenticator.
result = ((AuthenticatorBase) authenticator).authenticate(
request, response, config);
}
return result;
}
/**
* 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
*/
public boolean invokePostAuthenticateDelegate(HttpRequest request,
HttpResponse response,
Context context)
throws IOException {
boolean result = false;
ServerAuthContext sAC = null;
try {
if (helper != null) {
HttpServletRequest req = (HttpServletRequest)request.getRequest();
MessageInfo messageInfo =
(MessageInfo)req.getAttribute(MESSAGE_INFO);
if (messageInfo != null) {
//JSR 196 is enabled for this application
sAC = (ServerAuthContext)
messageInfo.getMap().get(SERVER_AUTH_CONTEXT);
if (sAC != null) {
AuthStatus authStatus =
sAC.secureResponse(messageInfo,
null); //null serviceSubject
result = AuthStatus.SUCCESS.equals(authStatus);
}
}
}
} catch(AuthException ex) {
IOException iex = new IOException();
iex.initCause(ex);
throw iex;
} finally {
if (helper != null && sAC != null) {
if (request instanceof HttpRequestWrapper) {
request.removeNote(Globals.WRAPPED_REQUEST);
}
if (response instanceof HttpResponseWrapper) {
request.removeNote(Globals.WRAPPED_RESPONSE);
}
}
}
return result;
}
/**
* Return <tt>true</tt> if a Security Extension is available.
* @return <tt>true</tt> if a Security Extension is available.
*/
public boolean isSecurityExtensionEnabled(){
if (helper == null) {
return false;
} else {
try {
return (helper.getServerAuthConfig() != null);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
}
}
/**
* This must be invoked after virtualServer is set.
*/
private HttpServletHelper getConfigHelper() {
Map map = new HashMap();
map.put(HttpServletConstants.WEB_BUNDLE, webDesc);
return new HttpServletHelper(getAppContextID(),
map, null, // null handler
_realmName, isSystemApp, defaultSystemProviderID);
}
/**
* This must be invoked after virtualServer is set.
*/
private String getAppContextID() {
return this.virtualServer.getName() + " " + webDesc.getContextRoot();
}
private boolean validate(HttpRequest request,
HttpResponse response,
LoginConfig config,
Authenticator authenticator)
throws IOException {
HttpServletRequest req = (HttpServletRequest)request.getRequest();
HttpServletResponse res = (HttpServletResponse)response.getResponse();
Subject subject = new Subject();
MessageInfo messageInfo = new HttpMessageInfo(req, res);
boolean rvalue = false;
boolean isMandatory = true;
try {
WebSecurityManager webSecMgr = getWebSecurityManager(true);
isMandatory = !webSecMgr.permitAll(req);
if (isMandatory) {
messageInfo.getMap().put(HttpServletConstants.IS_MANDATORY,
Boolean.TRUE.toString());
}
ServerAuthContext sAC =
helper.getServerAuthContext(messageInfo,
null); // null serviceSubject
if (sAC != null) {
AuthStatus authStatus =
sAC.validateRequest(messageInfo, subject,
null); // null serviceSubject
rvalue = AuthStatus.SUCCESS.equals(authStatus);
if (rvalue) { // cache it only if validateRequest = true
messageInfo.getMap().put(SERVER_AUTH_CONTEXT, sAC);
req.setAttribute(MESSAGE_INFO, messageInfo);
}
} else {
throw new AuthException("null ServerAuthContext");
}
} catch(AuthException ae) {
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,
"JAMC: http msg authentication fail",ae);
}
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
if (rvalue) {
Set principalSet = subject.getPrincipals();
// must be at least one new principal to establish
// non-default security context
if (principalSet != null && !principalSet.isEmpty() &&
!principalSetContainsOnlyAnonymousPrincipal(principalSet)) {
SecurityContext ctx = new SecurityContext(subject);
//XXX assuming no null principal here
Principal p = ctx.getCallerPrincipal();
WebPrincipal wp = new WebPrincipal(p,ctx);
try {
//XXX Keep it for reference
/*
if (this.sAC.managesSessions(sharedState)) {
// registration (via proxy) does not occur
// if context manages sessions
// record authentication information in the request
request.setAuthType(PROXY_AUTH_TYPE);
request.setUserPrincipal(wp);
} else {
AuthenticatorProxy proxy =
new AuthenticatorProxy(authenticator,wp);
proxy.authenticate(request,response,config);
}
*/
String authType = (String)messageInfo.getMap().get(
HttpServletHelper.AUTH_TYPE);
boolean register = messageInfo.getMap().containsKey(
HttpServletConstants.REGISTER_WITH_AUTHENTICATOR);
if (authType == null && config != null &&
config.getAuthMethod() != null) {
authType = config.getAuthMethod();
}
if (register) {
AuthenticatorProxy proxy = new AuthenticatorProxy
(authenticator, wp, authType);
proxy.authenticate(request,response,config);
} else {
request.setAuthType((authType == null) ?
PROXY_AUTH_TYPE: authType);
request.setUserPrincipal(wp);
}
} catch (LifecycleException le) {
_logger.log(Level.SEVERE,"[Web-Security] unable to register session",le);
}
HttpServletRequest newRequest = (HttpServletRequest)
messageInfo.getRequestMessage();
if (newRequest != req) {
request.setNote(Globals.WRAPPED_REQUEST,
new HttpRequestWrapper(request, newRequest));
}
HttpServletResponse newResponse = (HttpServletResponse)
messageInfo.getResponseMessage();
if (newResponse != res) {
request.setNote(Globals.WRAPPED_RESPONSE,
new HttpResponseWrapper(response,newResponse));
}
} else if (isMandatory) {
rvalue = false;
}
}
return rvalue;
}
/**
* get 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.
* @return the provider id or null.
*/
private static String getDefaultSystemProviderID() {
String p = System.getProperty(SYSTEM_HTTPSERVLET_SECURITY_PROVIDER);
if (p != null) {
p = p.trim();
if (p.length() == 0) {
p = null;
}
}
return p;
}
private static String PROXY_AUTH_TYPE = "PLUGGABLE_PROVIDER";
// inner class extends AuthenticatorBase such that session registration
// of webtier can be invoked by RealmAdapter after authentication
// by authentication module.
class AuthenticatorProxy extends AuthenticatorBase {
private AuthenticatorBase authBase;
private Principal principal;
private String authType;
public boolean getCache() {
return authBase.getCache();
}
public Container getContainer() {
return authBase.getContainer();
}
AuthenticatorProxy(Authenticator authenticator, Principal p, String authType)
throws LifecycleException
{
this.authBase = (AuthenticatorBase) authenticator;
this.principal = p;
this.authType =
authType == null ? RealmAdapter.PROXY_AUTH_TYPE : authType;
setCache(authBase.getCache());
setContainer(authBase.getContainer());
start(); //finds sso valve and sets its value in proxy
}
public boolean
authenticate(HttpRequest request,
HttpResponse response,
LoginConfig config) throws IOException
{
register(request,response,this.principal,this.authType,
this.principal.getName(),null);
return true;
}
}
private class HttpMessageInfo implements MessageInfo {
private Object request = null;
private Object response = null;
private Map map = new HashMap();
HttpMessageInfo() {
}
HttpMessageInfo(HttpServletRequest request,
HttpServletResponse response) {
this.request = request;
this.response = response;
}
public Object getRequestMessage() {
return request;
}
public Object getResponseMessage() {
return response;
}
public void setRequestMessage(Object request) {
this.request = request;
}
public void setResponseMessage(Object response) {
this.response = response;
}
public Map getMap() {
return map;
}
}
}