// ======================================================================== // $Id: SecurityConstraint.java,v 1.44 2005/08/13 00:01:24 gregwilkins Exp $ // Copyright 200-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ======================================================================== package org.browsermob.proxy.jetty.http; import org.apache.commons.logging.Log; import org.browsermob.proxy.jetty.jetty.servlet.FormAuthenticator; import org.browsermob.proxy.jetty.log.LogFactory; import org.browsermob.proxy.jetty.util.LazyList; import java.io.IOException; import java.io.Serializable; import java.security.Principal; import java.util.Collections; import java.util.List; /* ------------------------------------------------------------ */ /** Describe an auth and/or data constraint. * * @version $Revision: 1.44 $ * @author Greg Wilkins (gregw) */ public class SecurityConstraint implements Cloneable, Serializable { private static Log log= LogFactory.getLog(SecurityConstraint.class); /* ------------------------------------------------------------ */ public final static String __BASIC_AUTH= "BASIC"; public final static String __FORM_AUTH= "FORM"; public final static String __DIGEST_AUTH= "DIGEST"; public final static String __CERT_AUTH= "CLIENT_CERT"; public final static String __CERT_AUTH2= "CLIENT-CERT"; /* ------------------------------------------------------------ */ public final static int DC_UNSET= -1, DC_NONE= 0, DC_INTEGRAL= 1, DC_CONFIDENTIAL= 2; /* ------------------------------------------------------------ */ public final static String NONE= "NONE"; public final static String ANY_ROLE= "*"; /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /** Nobody user. * The Nobody UserPrincipal is used to indicate a partial state of * authentication. A request with a Nobody UserPrincipal will be allowed * past all authentication constraints - but will not be considered an * authenticated request. It can be used by Authenticators such as * FormAuthenticator to allow access to logon and error pages within an * authenticated URI tree. */ public static class Nobody implements Principal { public String getName() { return "Nobody"; } } public final static Nobody __NOBODY= new Nobody(); /* ------------------------------------------------------------ */ private String _name; private Object _methods; private Object _roles; private int _dataConstraint= DC_UNSET; private boolean _anyRole= false; private boolean _authenticate= false; private transient List _umMethods; private transient List _umRoles; /* ------------------------------------------------------------ */ /** Constructor. */ public SecurityConstraint() {} /* ------------------------------------------------------------ */ /** Conveniance Constructor. * @param name * @param role */ public SecurityConstraint(String name, String role) { setName(name); addRole(role); } /* ------------------------------------------------------------ */ /** * @param name */ public void setName(String name) { _name= name; } /* ------------------------------------------------------------ */ /** * @param method */ public synchronized void addMethod(String method) { _methods= LazyList.add(_methods, method); } /* ------------------------------------------------------------ */ public List getMethods() { if (_umMethods == null && _methods != null) _umMethods= Collections.unmodifiableList(LazyList.getList(_methods)); return _umMethods; } /* ------------------------------------------------------------ */ /** * @param method Method name. * @return True if this constraint applies to the method. If no * method has been set, then the constraint applies to all methods. */ public boolean forMethod(String method) { if (_methods == null) return true; for (int i= 0; i < LazyList.size(_methods); i++) if (LazyList.get(_methods, i).equals(method)) return true; return false; } /* ------------------------------------------------------------ */ /** * @param role The rolename. If the rolename is '*' all other * roles are removed and anyRole is set true and subsequent * addRole calls are ignored. * Authenticate is forced true by this call. */ public synchronized void addRole(String role) { _authenticate= true; if (ANY_ROLE.equals(role)) { _roles= null; _umRoles= null; _anyRole= true; } else if (!_anyRole) _roles= LazyList.add(_roles, role); } /* ------------------------------------------------------------ */ /** * @return True if any user role is permitted. */ public boolean isAnyRole() { return _anyRole; } /* ------------------------------------------------------------ */ /** * @return List of roles for this constraint. */ public List getRoles() { if (_umRoles == null && _roles != null) _umRoles= Collections.unmodifiableList(LazyList.getList(_roles)); return _umRoles; } /* ------------------------------------------------------------ */ /** * @param role * @return True if the constraint contains the role. */ public boolean hasRole(String role) { return LazyList.contains(_roles, role); } /* ------------------------------------------------------------ */ /** * @param authenticate True if users must be authenticated */ public void setAuthenticate(boolean authenticate) { _authenticate= authenticate; } /* ------------------------------------------------------------ */ /** * @return True if the constraint requires request authentication */ public boolean getAuthenticate() { return _authenticate; } /* ------------------------------------------------------------ */ /** * @return True if authentication required but no roles set */ public boolean isForbidden() { return _authenticate && !_anyRole && LazyList.size(_roles) == 0; } /* ------------------------------------------------------------ */ /** * @param c */ public void setDataConstraint(int c) { if (c < 0 || c > DC_CONFIDENTIAL) throw new IllegalArgumentException("Constraint out of range"); _dataConstraint= c; } /* ------------------------------------------------------------ */ /** * @return Data constrain indicator: 0=DC+NONE, 1=DC_INTEGRAL & 2=DC_CONFIDENTIAL */ public int getDataConstraint() { return _dataConstraint; } /* ------------------------------------------------------------ */ /** * @return True if a data constraint has been set. */ public boolean hasDataConstraint() { return _dataConstraint >= DC_NONE; } /* ------------------------------------------------------------ */ public Object clone() throws CloneNotSupportedException { SecurityConstraint sc = (SecurityConstraint) super.clone(); sc._umMethods=null; sc._umRoles=null; return sc; } /* ------------------------------------------------------------ */ public String toString() { return "SC{" + _name + "," + _methods + "," + (_anyRole ? "*" : (_roles == null ? "-" : _roles.toString())) + "," + (_dataConstraint == DC_NONE ? "NONE}" : (_dataConstraint == DC_INTEGRAL ? "INTEGRAL}" : "CONFIDENTIAL}")); } /* ------------------------------------------------------------ */ /** Check security contraints * @param constraints * @param authenticator * @param realm * @param pathInContext * @param request * @param response * @return false if the request has failed a security constraint or the authenticator has already sent a response. * @exception HttpException * @exception IOException */ public static boolean check( List constraints, Authenticator authenticator, UserRealm realm, String pathInContext, HttpRequest request, HttpResponse response) throws HttpException, IOException { // Combine data and auth constraints int dataConstraint= DC_NONE; Object roles= null; boolean unauthenticated= false; boolean forbidden= false; for (int c= 0; c < constraints.size(); c++) { SecurityConstraint sc= (SecurityConstraint)constraints.get(c); // Check the method applies if (!sc.forMethod(request.getMethod())) continue; // Combine data constraints. if (dataConstraint > DC_UNSET && sc.hasDataConstraint()) { if (sc.getDataConstraint() > dataConstraint) dataConstraint= sc.getDataConstraint(); } else dataConstraint= DC_UNSET; // ignore all other data constraints // Combine auth constraints. if (!unauthenticated && !forbidden) { if (sc.getAuthenticate()) { if (sc.isAnyRole()) { roles= ANY_ROLE; } else { List scr= sc.getRoles(); if (scr == null || scr.size() == 0) { forbidden= true; break; } else { if (roles != ANY_ROLE) { roles= LazyList.addCollection(roles, scr); } } } } else unauthenticated= true; } } // Does this forbid everything? if (forbidden && (!(authenticator instanceof FormAuthenticator) || !((FormAuthenticator)authenticator).isLoginOrErrorPage(pathInContext))) { HttpContext.sendContextError(response,HttpResponse.__403_Forbidden,null); return false; } // Handle data constraint if (dataConstraint > DC_NONE) { HttpConnection connection= request.getHttpConnection(); HttpListener listener= connection.getListener(); switch (dataConstraint) { case SecurityConstraint.DC_INTEGRAL : if (listener.isIntegral(connection)) break; if (listener.getIntegralPort() > 0) { String url= listener.getIntegralScheme() + "://" + request.getHost() + ":" + listener.getIntegralPort() + request.getPath(); if (request.getQuery() != null) url += "?" + request.getQuery(); response.setContentLength(0); response.sendRedirect(url); } else HttpContext.sendContextError(response,HttpResponse.__403_Forbidden,null); return false; case SecurityConstraint.DC_CONFIDENTIAL : if (listener.isConfidential(connection)) break; if (listener.getConfidentialPort() > 0) { String url= listener.getConfidentialScheme() + "://" + request.getHost() + ":" + listener.getConfidentialPort() + request.getPath(); if (request.getQuery() != null) url += "?" + request.getQuery(); response.setContentLength(0); response.sendRedirect(url); } else HttpContext.sendContextError(response,HttpResponse.__403_Forbidden,null); return false; default : HttpContext.sendContextError(response,HttpResponse.__403_Forbidden,null); return false; } } // Does it fail a role check? if (!unauthenticated && roles != null) { if (realm == null) { HttpContext.sendContextError(response,HttpResponse.__500_Internal_Server_Error,"Configuration error"); return false; } Principal user= null; // Handle pre-authenticated request if (request.getAuthType() != null && request.getAuthUser() != null) { // TODO - is this still needed??? user= request.getUserPrincipal(); if (user == null) user= realm.authenticate(request.getAuthUser(), null, request); if (user == null && authenticator != null) user= authenticator.authenticate(realm, pathInContext, request, response); } else if (authenticator != null) { // User authenticator. user= authenticator.authenticate(realm, pathInContext, request, response); } else { // don't know how authenticate log.warn("Mis-configured Authenticator for " + request.getPath()); HttpContext.sendContextError(response,HttpResponse.__500_Internal_Server_Error,"Configuration error"); } // If we still did not get a user if (user == null) return false; // Auth challenge or redirection already sent else if (user == __NOBODY) return true; // The Nobody user indicates authentication in transit. if (roles != ANY_ROLE) { boolean inRole= false; for (int r= LazyList.size(roles); r-- > 0;) { if (realm.isUserInRole(user, (String)LazyList.get(roles, r))) { inRole= true; break; } } if (!inRole) { log.warn("AUTH FAILURE: role for " + user.getName()); if ("BASIC".equalsIgnoreCase(authenticator.getAuthMethod())) ((BasicAuthenticator)authenticator).sendChallenge(realm, response); else HttpContext.sendContextError(response,HttpResponse.__403_Forbidden,"User not in required role"); return false; // role failed. } } } else { request.setUserPrincipal(HttpRequest.__NOT_CHECKED); } return true; } }