001    package hirondelle.web4j.security;
002    
003    import static hirondelle.web4j.util.Consts.NOT_FOUND;
004    import hirondelle.web4j.action.Operation;
005    import hirondelle.web4j.readconfig.Config;
006    import hirondelle.web4j.request.RequestParser;
007    import hirondelle.web4j.util.Util;
008    import hirondelle.web4j.util.WebUtil;
009    
010    import java.util.List;
011    
012    /**
013     Default implementation of {@link UntrustedProxyForUserId}.
014    
015     <P>This implementation depends on settings in <tt>web.xml</tt>, which are read in on startup.
016      Later, each request URL is parsed by {@link #usesUntrustedIdentifier(RequestParser)}, 
017      and an attempt is made to find a match to the aforementioned settings in <tt>web.xml</tt>.
018      
019      <P>This class uses settings in <tt>web.xml</tt> to define requests having ownership constraints that use an untrusted proxy 
020      for the user id. It uses a <i>roughly</i> similar style as used for role-based constraints.
021      Here is an example of a number of several such ownership constraints defined in <tt>web.xml</tt>:<PRE>&lt;init-param&gt;
022      &lt;description&gt;
023        Operations having an ownership constraint that uses an untrusted identifier. 
024      &lt;/description&gt;
025      &lt;param-name&gt;UntrustedProxyForUserId&lt;/param-name&gt;
026      &lt;param-value&gt;
027        FoodAction.*
028        VacationAction.add
029        VacationAction.delete
030      &lt;/param-value&gt;
031    &lt;/init-param&gt;
032    </PRE>
033    
034      <P>Each line is treated as a separate constraint, one per line. You can define as many as required.
035      The period character separates the 'noun' (the Action) from the 'verb' (the {@link Operation}).
036     
037      <P>The special '*' character refers to all verbs/operations attached to a given noun/action.
038    */
039    public final class UntrustedProxyForUserIdImpl implements UntrustedProxyForUserId {
040      
041      /** 
042        Return <tt>true</tt> only if the given request matches one of the items defined by the <tt>UntrustedProxyForUserId</tt> setting 
043        in <tt>web.xml</tt>. 
044        
045        <P>For example, given the URL : 
046        <PRE>'.../VacationAction.list?X=Y'</PRE>
047        this method will parse the URL into a 'noun' and a 'verb' :
048     <PRE>noun: 'VacationAction'
049    verb: 'list'</PRE>
050    
051        It will then compare the noun-and-verb to the settings defined in <tt>web.xml</tt>.
052        If there's a match, then this method returns <tt>true</tt>.
053      */
054      public boolean usesUntrustedIdentifier(RequestParser aRequestParser) {
055        boolean result = false; //by default, there is no ownership constraint
056        String noun = extractNoun(aRequestParser); //WebUtil needs response too! servlet path + path info?
057        if( isRestrictedRequest(noun) ){
058          List<String> restrictedVerbs = fConfig.getUntrustedProxyForUserId().get(noun);
059          if ( hasAllOperationsRestricted(restrictedVerbs) ) {
060            result = true;
061          }
062          else {
063            String verb = extractVerb(aRequestParser);
064            if ( restrictedVerbs.contains(verb) ) {
065              result = true;
066            }
067          }
068        }
069        return result;
070      }
071      
072      /** Special character denoting all operations/verbs. */
073      public static final String ALL_OPERATIONS = "*";
074      
075      // PRIVATE 
076      
077      private Config fConfig = new Config();
078      
079      private boolean isRestrictedRequest(String aNoun){
080        return fConfig.getUntrustedProxyForUserId().containsKey(aNoun);
081      }
082      
083      private boolean hasAllOperationsRestricted(List<String> aVerbs){ 
084        return aVerbs.contains(ALL_OPERATIONS);
085      }
086      
087      /**
088       For the example URL '.../BlahAction.do?X=Y', this method returns 'BlahAction' as the noun.
089       Relies on the presence of '/' and '.' characters.
090      */
091      private String extractNoun(RequestParser aRequestParser){
092        String uri = getURI(aRequestParser);
093        int firstPeriod = uri.indexOf(".");
094        if( firstPeriod == NOT_FOUND ) {
095          throw new RuntimeException("Cannot find '.' character in URL: " + Util.quote(uri));
096        }
097        int lastSlash = uri.lastIndexOf("/");
098        if( lastSlash == NOT_FOUND ) {
099          throw new RuntimeException("Cannot find '/' character in URL: " + Util.quote(uri));
100        }
101        return uri.substring(lastSlash + 1, firstPeriod);
102      }
103    
104      /** Return the part of the URL after the '.' character, but before any '?' character (if present). */
105      private String extractVerb(RequestParser aRequestParser){
106        return WebUtil.getFileExtension(getURI(aRequestParser));
107      }
108      
109      private String getURI(RequestParser aRequestParser){
110        return aRequestParser.getRequest().getRequestURI();
111      }
112    }