From c7e36470c528904119893b27b559e07f66e30bbe Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Fri, 28 Sep 2012 20:16:52 +1000 Subject: 390503 http-method-omission element not being processed --- .../jetty/security/ConstraintSecurityHandler.java | 413 +++++++++++++++++++-- 1 file changed, 379 insertions(+), 34 deletions(-) (limited to 'jetty-security/src/main/java') diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java index 0ca83b1962..94f70f565c 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/ConstraintSecurityHandler.java @@ -19,16 +19,25 @@ package org.eclipse.jetty.security; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import javax.servlet.HttpConstraintElement; +import javax.servlet.HttpMethodConstraintElement; +import javax.servlet.ServletSecurityElement; +import javax.servlet.annotation.ServletSecurity.EmptyRoleSemantic; +import javax.servlet.annotation.ServletSecurity.TransportGuarantee; + import org.eclipse.jetty.http.PathMap; import org.eclipse.jetty.server.AbstractHttpConnection; import org.eclipse.jetty.server.Connector; @@ -48,11 +57,217 @@ import org.eclipse.jetty.util.security.Constraint; */ public class ConstraintSecurityHandler extends SecurityHandler implements ConstraintAware { + private static final String OMISSION_SUFFIX = ".omission"; + private final List _constraintMappings= new CopyOnWriteArrayList(); private final Set _roles = new CopyOnWriteArraySet(); private final PathMap _constraintMap = new PathMap(); private boolean _strict = true; + + + /* ------------------------------------------------------------ */ + /** + * @return + */ + public static Constraint createConstraint() + { + return new Constraint(); + } + + /* ------------------------------------------------------------ */ + /** + * @param constraint + * @return + */ + public static Constraint createConstraint(Constraint constraint) + { + try + { + return (Constraint)constraint.clone(); + } + catch (CloneNotSupportedException e) + { + throw new IllegalStateException (e); + } + } + + /* ------------------------------------------------------------ */ + /** + * Create a security constraint + * + * @param name + * @param authenticate + * @param roles + * @param dataConstraint + * @return + */ + public static Constraint createConstraint (String name, boolean authenticate, String[] roles, int dataConstraint) + { + Constraint constraint = createConstraint(); + if (name != null) + constraint.setName(name); + constraint.setAuthenticate(authenticate); + constraint.setRoles(roles); + constraint.setDataConstraint(dataConstraint); + return constraint; + } + + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param element + * @return + */ + public static Constraint createConstraint (String name, HttpConstraintElement element) + { + return createConstraint(name, element.getRolesAllowed(), element.getEmptyRoleSemantic(), element.getTransportGuarantee()); + } + + + /* ------------------------------------------------------------ */ + /** + * @param name + * @param rolesAllowed + * @param permitOrDeny + * @param transport + * @return + */ + public static Constraint createConstraint (String name, String[] rolesAllowed, EmptyRoleSemantic permitOrDeny, TransportGuarantee transport) + { + Constraint constraint = createConstraint(); + + if (rolesAllowed == null || rolesAllowed.length==0) + { + if (permitOrDeny.equals(EmptyRoleSemantic.DENY)) + { + //Equivalent to with no roles + constraint.setName(name+"-Deny"); + constraint.setAuthenticate(true); + } + else + { + //Equivalent to no + constraint.setName(name+"-Permit"); + constraint.setAuthenticate(false); + } + } + else + { + //Equivalent to with list of s + constraint.setAuthenticate(true); + constraint.setRoles(rolesAllowed); + constraint.setName(name+"-RolesAllowed"); + } + + //Equivalent to //CONFIDENTIAL + constraint.setDataConstraint((transport.equals(TransportGuarantee.CONFIDENTIAL)?Constraint.DC_CONFIDENTIAL:Constraint.DC_NONE)); + return constraint; + } + + + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec + * @param constraintMappings + * @return + */ + public static List getConstraintMappingsForPath(String pathSpec, List constraintMappings) + { + if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0) + return Collections.emptyList(); + + List mappings = new ArrayList(); + for (ConstraintMapping mapping:constraintMappings) + { + if (pathSpec.equals(mapping.getPathSpec())) + { + mappings.add(mapping); + } + } + return mappings; + } + + + /* ------------------------------------------------------------ */ + /** + * @param pathSpec + * @param constraintMappings + * @return + */ + public static List removeConstraintMappingsForPath(String pathSpec, List constraintMappings) + { + if (pathSpec == null || "".equals(pathSpec.trim()) || constraintMappings == null || constraintMappings.size() == 0) + return Collections.emptyList(); + + List mappings = new ArrayList(); + for (ConstraintMapping mapping:constraintMappings) + { + //Remove the matching mappings by only copying in non-matching mappings + if (!pathSpec.equals(mapping.getPathSpec())) + { + mappings.add(mapping); + } + } + return mappings; + } + + + + /* ------------------------------------------------------------ */ + /** Generate Constraints and ContraintMappings for the given url pattern and ServletSecurityElement + * + * @param name + * @param pathSpec + * @param securityElement + * @return + */ + public static List createConstraintsWithMappingsForPath (String name, String pathSpec, ServletSecurityElement securityElement) + { + List mappings = new ArrayList(); + + //Create a constraint that will describe the default case (ie if not overridden by specific HttpMethodConstraints) + Constraint constraint = ConstraintSecurityHandler.createConstraint(name, securityElement); + + //Create a mapping for the pathSpec for the default case + ConstraintMapping defaultMapping = new ConstraintMapping(); + defaultMapping.setPathSpec(pathSpec); + defaultMapping.setConstraint(constraint); + mappings.add(defaultMapping); + + //See Spec 13.4.1.2 p127 + List methodOmissions = new ArrayList(); + + //make constraint mappings for this url for each of the HttpMethodConstraintElements + Collection methodConstraints = securityElement.getHttpMethodConstraints(); + if (methodConstraints != null) + { + for (HttpMethodConstraintElement methodConstraint:methodConstraints) + { + //Make a Constraint that captures the and elements supplied for the HttpMethodConstraintElement + Constraint mconstraint = ConstraintSecurityHandler.createConstraint(name, methodConstraint); + ConstraintMapping mapping = new ConstraintMapping(); + mapping.setConstraint(mconstraint); + mapping.setPathSpec(pathSpec); + if (methodConstraint.getMethodName() != null) + { + mapping.setMethod(methodConstraint.getMethodName()); + //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint + methodOmissions.add(methodConstraint.getMethodName()); + } + mappings.add(mapping); + } + } + //See spec 13.4.1.2 p127 - add an omission for every method name to the default constraint + if (methodOmissions.size() > 0) + defaultMapping.setMethodOmissions(methodOmissions.toArray(new String[methodOmissions.size()])); + + return mappings; + } + + /* ------------------------------------------------------------ */ /** Get the strict mode. * @return true if the security handler is running in strict mode. @@ -232,7 +447,9 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } super.doStart(); } - + + + /* ------------------------------------------------------------ */ @Override protected void doStop() throws Exception { @@ -241,7 +458,15 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr _roles.clear(); super.doStop(); } - + + + /* ------------------------------------------------------------ */ + /** + * Create and combine the constraint with the existing processed + * constraints. + * + * @param mapping + */ protected void processConstraintMapping(ConstraintMapping mapping) { Map mappings = (Map)_constraintMap.get(mapping.getPathSpec()); @@ -253,8 +478,15 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr RoleInfo allMethodsRoleInfo = mappings.get(null); if (allMethodsRoleInfo != null && allMethodsRoleInfo.isForbidden()) return; + + if (mapping.getMethodOmissions() != null && mapping.getMethodOmissions().length > 0) + { + + processConstraintMappingWithMethodOmissions(mapping, mappings); + return; + } - String httpMethod = mapping.getMethod(); + String httpMethod = mapping.getMethod(); RoleInfo roleInfo = mappings.get(httpMethod); if (roleInfo == null) { @@ -268,10 +500,10 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr if (roleInfo.isForbidden()) return; - Constraint constraint = mapping.getConstraint(); - boolean forbidden = constraint.isForbidden(); - roleInfo.setForbidden(forbidden); - if (forbidden) + //add in info from the constraint + configureRoleInfo(roleInfo, mapping); + + if (roleInfo.isForbidden()) { if (httpMethod == null) { @@ -281,50 +513,120 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } else { - UserDataConstraint userDataConstraint = UserDataConstraint.get(constraint.getDataConstraint()); - roleInfo.setUserDataConstraint(userDataConstraint); + //combine with any entry that covers all methods + if (httpMethod == null) + { + for (Map.Entry entry : mappings.entrySet()) + { + if (entry.getKey() != null) + { + RoleInfo specific = entry.getValue(); + specific.combine(roleInfo); + } + } + } + } + } + + /* ------------------------------------------------------------ */ + /** Constraints that name method omissions are dealt with differently. + * We create an entry in the mappings with key "method.omission". This entry + * is only ever combined with other omissions for the same method to produce a + * consolidated RoleInfo. Then, when we wish to find the relevant constraints for + * a given Request (in prepareConstraintInfo()), we consult 3 types of entries in + * the mappings: an entry that names the method of the Request specifically, an + * entry that names constraints that apply to all methods, entries of the form + * method.omission, where the method of the Request is not named in the omission. + * @param mapping + * @param mappings + */ + protected void processConstraintMappingWithMethodOmissions (ConstraintMapping mapping, Map mappings) + { + String[] omissions = mapping.getMethodOmissions(); - boolean checked = constraint.getAuthenticate(); - roleInfo.setChecked(checked); - if (roleInfo.isChecked()) + for (String omission:omissions) + { + //for each method omission, see if there is already a RoleInfo for it in mappings + RoleInfo ri = mappings.get(omission+OMISSION_SUFFIX); + if (ri == null) { - if (constraint.isAnyRole()) + //if not, make one + ri = new RoleInfo(); + mappings.put(omission+OMISSION_SUFFIX, ri); + } + + //initialize RoleInfo or combine from ConstraintMapping + configureRoleInfo(ri, mapping); + } + } + + + /* ------------------------------------------------------------ */ + /** + * Initialize or update the RoleInfo from the constraint + * @param ri + * @param mapping + */ + protected void configureRoleInfo (RoleInfo ri, ConstraintMapping mapping) + { + Constraint constraint = mapping.getConstraint(); + boolean forbidden = constraint.isForbidden(); + ri.setForbidden(forbidden); + + //set up the data constraint (NOTE: must be done after setForbidden, as it nulls out the data constraint + //which we need in order to do combining of omissions in prepareConstraintInfo + UserDataConstraint userDataConstraint = UserDataConstraint.get(mapping.getConstraint().getDataConstraint()); + ri.setUserDataConstraint(userDataConstraint); + + + //if forbidden, no point setting up roles + if (!ri.isForbidden()) + { + //add in the roles + boolean checked = mapping.getConstraint().getAuthenticate(); + ri.setChecked(checked); + if (ri.isChecked()) + { + if (mapping.getConstraint().isAnyRole()) { if (_strict) { // * means "all defined roles" for (String role : _roles) - roleInfo.addRole(role); + ri.addRole(role); } else // * means any role - roleInfo.setAnyRole(true); + ri.setAnyRole(true); } else { - String[] newRoles = constraint.getRoles(); + String[] newRoles = mapping.getConstraint().getRoles(); for (String role : newRoles) { if (_strict &&!_roles.contains(role)) throw new IllegalArgumentException("Attempt to use undeclared role: " + role + ", known roles: " + _roles); - roleInfo.addRole(role); - } - } - } - if (httpMethod == null) - { - for (Map.Entry entry : mappings.entrySet()) - { - if (entry.getKey() != null) - { - RoleInfo specific = entry.getValue(); - specific.combine(roleInfo); + ri.addRole(role); } } } } } - + + + /* ------------------------------------------------------------ */ + /** + * Find constraints that apply to the given path. + * In order to do this, we consult 3 different types of information stored in the mappings for each path - each mapping + * represents a merged set of user data constraints, roles etc -: + *
    + *
  1. A mapping of an exact method name
  2. + *
  3. A mapping will null key that matches every method name
  4. + *
  5. Mappings with keys of the form "method.omission" that indicates it will match every method name EXCEPT that given
  6. + *
+ * + * @see org.eclipse.jetty.security.SecurityHandler#prepareConstraintInfo(java.lang.String, org.eclipse.jetty.server.Request) + */ protected Object prepareConstraintInfo(String pathInContext, Request request) { Map mappings = (Map)_constraintMap.match(pathInContext); @@ -334,13 +636,46 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr String httpMethod = request.getMethod(); RoleInfo roleInfo = mappings.get(httpMethod); if (roleInfo == null) - roleInfo = mappings.get(null); + { + //No specific http-method names matched + List applicableConstraints = new ArrayList(); + + //Get info for constraint that matches all methods if it exists + RoleInfo all = mappings.get(null); + if (all != null) + applicableConstraints.add(all); + + + //Get info for constraints that name method omissions where target method name is not omitted + //(ie matches because target method is not omitted, hence considered covered by the constraint) + for (Entry entry: mappings.entrySet()) + { + if (entry.getKey() != null && entry.getKey().contains(OMISSION_SUFFIX) && !(httpMethod+OMISSION_SUFFIX).equals(entry.getKey())) + applicableConstraints.add(entry.getValue()); + } + + if (applicableConstraints.size() == 1) + roleInfo = applicableConstraints.get(0); + else + { + roleInfo = new RoleInfo(); + roleInfo.setUserDataConstraint(UserDataConstraint.None); + + for (RoleInfo r:applicableConstraints) + roleInfo.combine(r); + } + + } return roleInfo; } - return null; } - + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.SecurityHandler#checkUserDataPermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object) + */ protected boolean checkUserDataPermissions(String pathInContext, Request request, Response response, Object constraintInfo) throws IOException { if (constraintInfo == null) @@ -404,7 +739,11 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } } - + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.SecurityHandler#isAuthMandatory(org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object) + */ protected boolean isAuthMandatory(Request baseRequest, Response base_response, Object constraintInfo) { if (constraintInfo == null) @@ -413,7 +752,12 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr } return ((RoleInfo)constraintInfo).isChecked(); } - + + + /* ------------------------------------------------------------ */ + /** + * @see org.eclipse.jetty.security.SecurityHandler#checkWebResourcePermissions(java.lang.String, org.eclipse.jetty.server.Request, org.eclipse.jetty.server.Response, java.lang.Object, org.eclipse.jetty.server.UserIdentity) + */ @Override protected boolean checkWebResourcePermissions(String pathInContext, Request request, Response response, Object constraintInfo, UserIdentity userIdentity) throws IOException @@ -454,4 +798,5 @@ public class ConstraintSecurityHandler extends SecurityHandler implements Constr getBeans(), TypeUtil.asList(getHandlers())); } + } -- cgit v1.2.3