All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.security.ConstraintSecurityHandler Maven / Gradle / Ivy

There is a newer version: 11.0.0.beta1
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

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.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;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.StringMap;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.security.Constraint;

/* ------------------------------------------------------------ */
/**
 * Handler to enforce SecurityConstraints. This implementation is servlet spec
 * 3.0 compliant and precomputes the constraint combinations for runtime
 * efficiency.
 *
 */
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;
    }
    
    
    /* ------------------------------------------------------------ */
    /** Take out of the constraint mappings those that match the 
     * given path.
     * 
     * @param pathSpec
     * @param constraintMappings a new list minus the matching constraints
     * @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.
     */
    public boolean isStrict()
    {
        return _strict;
    }

    /* ------------------------------------------------------------ */
    /** Set the strict mode of the security handler.
     * 

* When in strict mode (the default), the full servlet specification * will be implemented. * If not in strict mode, some additional flexibility in configuration * is allowed:

    *
  • All users do not need to have a role defined in the deployment descriptor *
  • The * role in a constraint applies to ANY role rather than all roles defined in * the deployment descriptor. *
* * @param strict the strict to set * @see #setRoles(Set) * @see #setConstraintMappings(List, Set) */ public void setStrict(boolean strict) { _strict = strict; } /* ------------------------------------------------------------ */ /** * @return Returns the constraintMappings. */ public List getConstraintMappings() { return _constraintMappings; } /* ------------------------------------------------------------ */ public Set getRoles() { return _roles; } /* ------------------------------------------------------------ */ /** * Process the constraints following the combining rules in Servlet 3.0 EA * spec section 13.7.1 Note that much of the logic is in the RoleInfo class. * * @param constraintMappings * The constraintMappings to set, from which the set of known roles * is determined. */ public void setConstraintMappings(List constraintMappings) { setConstraintMappings(constraintMappings,null); } /** * Process the constraints following the combining rules in Servlet 3.0 EA * spec section 13.7.1 Note that much of the logic is in the RoleInfo class. * * @param constraintMappings * The constraintMappings to set as array, from which the set of known roles * is determined. Needed to retain API compatibility for 7.x */ public void setConstraintMappings( ConstraintMapping[] constraintMappings ) { setConstraintMappings( Arrays.asList(constraintMappings), null); } /* ------------------------------------------------------------ */ /** * Process the constraints following the combining rules in Servlet 3.0 EA * spec section 13.7.1 Note that much of the logic is in the RoleInfo class. * * @param constraintMappings * The constraintMappings to set. * @param roles The known roles (or null to determine them from the mappings) */ public void setConstraintMappings(List constraintMappings, Set roles) { _constraintMappings.clear(); _constraintMappings.addAll(constraintMappings); if (roles==null) { roles = new HashSet(); for (ConstraintMapping cm : constraintMappings) { String[] cmr = cm.getConstraint().getRoles(); if (cmr!=null) { for (String r : cmr) if (!"*".equals(r)) roles.add(r); } } } setRoles(roles); if (isStarted()) { for (ConstraintMapping mapping : _constraintMappings) { processConstraintMapping(mapping); } } } /* ------------------------------------------------------------ */ /** * Set the known roles. * This may be overridden by a subsequent call to {@link #setConstraintMappings(ConstraintMapping[])} or * {@link #setConstraintMappings(List, Set)}. * @see #setStrict(boolean) * @param roles The known roles (or null to determine them from the mappings) */ public void setRoles(Set roles) { _roles.clear(); _roles.addAll(roles); } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.security.ConstraintAware#addConstraintMapping(org.eclipse.jetty.security.ConstraintMapping) */ public void addConstraintMapping(ConstraintMapping mapping) { _constraintMappings.add(mapping); if (mapping.getConstraint()!=null && mapping.getConstraint().getRoles()!=null) for (String role : mapping.getConstraint().getRoles()) addRole(role); if (isStarted()) { processConstraintMapping(mapping); } } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.security.ConstraintAware#addRole(java.lang.String) */ public void addRole(String role) { boolean modified = _roles.add(role); if (isStarted() && modified && _strict) { // Add the new role to currently defined any role role infos for (Map map : (Collection>)_constraintMap.values()) { for (RoleInfo info : map.values()) { if (info.isAnyRole()) info.addRole(role); } } } } /* ------------------------------------------------------------ */ /** * @see org.eclipse.jetty.security.SecurityHandler#doStart() */ @Override protected void doStart() throws Exception { _constraintMap.clear(); if (_constraintMappings!=null) { for (ConstraintMapping mapping : _constraintMappings) { processConstraintMapping(mapping); } } super.doStart(); } /* ------------------------------------------------------------ */ @Override protected void doStop() throws Exception { _constraintMap.clear(); _constraintMappings.clear(); _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()); if (mappings == null) { mappings = new StringMap(); _constraintMap.put(mapping.getPathSpec(),mappings); } 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(); RoleInfo roleInfo = mappings.get(httpMethod); if (roleInfo == null) { roleInfo = new RoleInfo(); mappings.put(httpMethod,roleInfo); if (allMethodsRoleInfo != null) { roleInfo.combine(allMethodsRoleInfo); } } if (roleInfo.isForbidden()) return; //add in info from the constraint configureRoleInfo(roleInfo, mapping); if (roleInfo.isForbidden()) { if (httpMethod == null) { mappings.clear(); mappings.put(null,roleInfo); } } else { //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(); 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 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) ri.addRole(role); } else // * means any role ri.setAnyRole(true); } else { 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); 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); if (mappings != null) { String httpMethod = request.getMethod(); RoleInfo roleInfo = mappings.get(httpMethod); if (roleInfo == 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) return true; RoleInfo roleInfo = (RoleInfo)constraintInfo; if (roleInfo.isForbidden()) return false; UserDataConstraint dataConstraint = roleInfo.getUserDataConstraint(); if (dataConstraint == null || dataConstraint == UserDataConstraint.None) { return true; } AbstractHttpConnection connection = AbstractHttpConnection.getCurrentConnection(); Connector connector = connection.getConnector(); if (dataConstraint == UserDataConstraint.Integral) { if (connector.isIntegral(request)) return true; if (connector.getIntegralPort() > 0) { String url = connector.getIntegralScheme() + "://" + request.getServerName() + ":" + connector.getIntegralPort() + request.getRequestURI(); if (request.getQueryString() != null) url += "?" + request.getQueryString(); response.setContentLength(0); response.sendRedirect(url); } else response.sendError(Response.SC_FORBIDDEN,"!Integral"); request.setHandled(true); return false; } else if (dataConstraint == UserDataConstraint.Confidential) { if (connector.isConfidential(request)) return true; if (connector.getConfidentialPort() > 0) { String url = connector.getConfidentialScheme() + "://" + request.getServerName() + ":" + connector.getConfidentialPort() + request.getRequestURI(); if (request.getQueryString() != null) url += "?" + request.getQueryString(); response.setContentLength(0); response.sendRedirect(url); } else response.sendError(Response.SC_FORBIDDEN,"!Confidential"); request.setHandled(true); return false; } else { throw new IllegalArgumentException("Invalid dataConstraint value: " + dataConstraint); } } /* ------------------------------------------------------------ */ /** * @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) { return false; } 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 { if (constraintInfo == null) { return true; } RoleInfo roleInfo = (RoleInfo)constraintInfo; if (!roleInfo.isChecked()) { return true; } if (roleInfo.isAnyRole() && request.getAuthType()!=null) return true; for (String role : roleInfo.getRoles()) { if (userIdentity.isUserInRole(role, null)) return true; } return false; } /* ------------------------------------------------------------ */ @Override public void dump(Appendable out,String indent) throws IOException { dumpThis(out); dump(out,indent, Collections.singleton(getLoginService()), Collections.singleton(getIdentityService()), Collections.singleton(getAuthenticator()), Collections.singleton(_roles), _constraintMap.entrySet(), getBeans(), TypeUtil.asList(getHandlers())); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy