package hirondelle.web4j.security;

import static hirondelle.web4j.util.Consts.NOT_FOUND;
import hirondelle.web4j.action.Operation;
import hirondelle.web4j.readconfig.Config;
import hirondelle.web4j.request.RequestParser;
import hirondelle.web4j.util.Util;
import hirondelle.web4j.util.WebUtil;

import java.util.List;

/**
 Default implementation of {@link UntrustedProxyForUserId}.

 <P>This implementation depends on settings in <tt>web.xml</tt>, which are read in on startup.
  Later, each request URL is parsed by {@link #usesUntrustedIdentifier(RequestParser)}, 
  and an attempt is made to find a match to the aforementioned settings in <tt>web.xml</tt>.
  
  <P>This class uses settings in <tt>web.xml</tt> to define requests having ownership constraints that use an untrusted proxy 
  for the user id. It uses a <i>roughly</i> similar style as used for role-based constraints.
  Here is an example of a number of several such ownership constraints defined in <tt>web.xml</tt>:<PRE>&lt;init-param&gt;
  &lt;description&gt;
    Operations having an ownership constraint that uses an untrusted identifier. 
  &lt;/description&gt;
  &lt;param-name&gt;UntrustedProxyForUserId&lt;/param-name&gt;
  &lt;param-value&gt;
    FoodAction.*
    VacationAction.add
    VacationAction.delete
  &lt;/param-value&gt;
&lt;/init-param&gt;
</PRE>

  <P>Each line is treated as a separate constraint, one per line. You can define as many as required.
  The period character separates the 'noun' (the Action) from the 'verb' (the {@link Operation}).
 
  <P>The special '*' character refers to all verbs/operations attached to a given noun/action.
*/
public final class UntrustedProxyForUserIdImpl implements UntrustedProxyForUserId {
  
  /** 
    Return <tt>true</tt> only if the given request matches one of the items defined by the <tt>UntrustedProxyForUserId</tt> setting 
    in <tt>web.xml</tt>. 
    
    <P>For example, given the URL : 
    <PRE>'.../VacationAction.list?X=Y'</PRE>
    this method will parse the URL into a 'noun' and a 'verb' :
 <PRE>noun: 'VacationAction'
verb: 'list'</PRE>

    It will then compare the noun-and-verb to the settings defined in <tt>web.xml</tt>.
    If there's a match, then this method returns <tt>true</tt>.
  */
  public boolean usesUntrustedIdentifier(RequestParser aRequestParser) {
    boolean result = false; //by default, there is no ownership constraint
    String noun = extractNoun(aRequestParser); //WebUtil needs response too! servlet path + path info?
    if( isRestrictedRequest(noun) ){
      List<String> restrictedVerbs = fConfig.getUntrustedProxyForUserId().get(noun);
      if ( hasAllOperationsRestricted(restrictedVerbs) ) {
        result = true;
      }
      else {
        String verb = extractVerb(aRequestParser);
        if ( restrictedVerbs.contains(verb) ) {
          result = true;
        }
      }
    }
    return result;
  }
  
  /** Special character denoting all operations/verbs. */
  public static final String ALL_OPERATIONS = "*";
  
  // PRIVATE 
  
  private Config fConfig = new Config();
  
  private boolean isRestrictedRequest(String aNoun){
    return fConfig.getUntrustedProxyForUserId().containsKey(aNoun);
  }
  
  private boolean hasAllOperationsRestricted(List<String> aVerbs){ 
    return aVerbs.contains(ALL_OPERATIONS);
  }
  
  /**
   For the example URL '.../BlahAction.do?X=Y', this method returns 'BlahAction' as the noun.
   Relies on the presence of '/' and '.' characters.
  */
  private String extractNoun(RequestParser aRequestParser){
    String uri = getURI(aRequestParser);
    int firstPeriod = uri.indexOf(".");
    if( firstPeriod == NOT_FOUND ) {
      throw new RuntimeException("Cannot find '.' character in URL: " + Util.quote(uri));
    }
    int lastSlash = uri.lastIndexOf("/");
    if( lastSlash == NOT_FOUND ) {
      throw new RuntimeException("Cannot find '/' character in URL: " + Util.quote(uri));
    }
    return uri.substring(lastSlash + 1, firstPeriod);
  }

  /** Return the part of the URL after the '.' character, but before any '?' character (if present). */
  private String extractVerb(RequestParser aRequestParser){
    return WebUtil.getFileExtension(getURI(aRequestParser));
  }
  
  private String getURI(RequestParser aRequestParser){
    return aRequestParser.getRequest().getRequestURI();
  }
}
