[prev in list] [next in list] [prev in thread] [next in thread] 

List:       jakarta-commons-dev
Subject:    cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestAuthenticator.java
From:       jsdever () apache ! org
Date:       2002-09-30 19:42:13
[Download RAW message or body]

jsdever     2002/09/30 12:42:13

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        Authenticator.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestAuthenticator.java
  Log:
  Refactor the parsing of the authentication header into a challenge map.
  
  -Added a static method parseAuthenticationHeader
  -Fixed handling of realm strings containing a ',' character
  -Recognized NTLM as the most secure authentication method, followed by Digest
  -Added a test case with a long realm string
  
  Contributed by: Jeff Dever
  
  Revision  Changes    Path
  1.30      +107 -48   \
jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java  \
  Index: Authenticator.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v
  retrieving revision 1.29
  retrieving revision 1.30
  diff -u -r1.29 -r1.30
  --- Authenticator.java	26 Sep 2002 11:40:16 -0000	1.29
  +++ Authenticator.java	30 Sep 2002 19:42:13 -0000	1.30
  @@ -348,7 +348,7 @@
   		}
   
           if (null == cred) {
  -            throw new HttpException("No credentials available for NTLM"
  +            throw new HttpException("No credentials available for NTLM "
                   + "authentication.");
           } else {
   			try {
  @@ -502,6 +502,94 @@
           return mapTokens;
       }
   
  +    /** 
  +     * Parses an authenticate header into a map of authentication challenges
  +     * keyed on the lowercase authentication scheme.
  +     *
  +     * @param authHeader the authentication header
  +     * @return a map of authentication challenges or an empty map if the
  +     * <i>authHeader</i> is <tt>null</tt> or contains a <tt>null</tt> value
  +     *
  +     * @since 2.0
  +     */
  +    private static Map parseAuthenticateHeader(Header authHeader) {
  +
  +        log.trace("enter parseAuthenticateHeader(Header)");
  +        
  +        if (log.isDebugEnabled()) {
  +            log.debug("Attempting to parse authenticate header: '"
  +                      + authHeader + "'");
  +        }
  +        if (authHeader == null || authHeader.getValue() == null) {
  +            return new Hashtable(0);
  +        }
  +
  +        String authValue = authHeader.getValue().trim();
  +        Map challengeMap = new Hashtable(7);
  +
  +        final int authValueLength = authValue.length();
  +        int s = authValueLength > 0 ? 0 : -1; //start position
  +        int q1 = 0; //position of quote 1
  +        int q2 = 0; //position of quote 2
  +        int c; //position of comma
  +        int b; //position of blank
  +
  +        String challenge = null; //an authentication challenge
  +        String scheme = null; //the scheme from the challenge
  +
  +        try {
  +            while (s >= 0 && s < authValueLength) {
  +                q1 = authValue.indexOf('"', s);
  +                q2 = authValue.indexOf('"', q1+1);
  +                c = authValue.indexOf(',', s);
  +
  +                //skip any commas in quotes
  +                while (c > q1 && c < q2 && c > 0) {
  +                    c = authValue.indexOf(',', c+1);
  +                }
  +                
  +                //set c to be the end if there is no comma
  +                if (c < 0) {
  +                    c = authValueLength;
  +                }
  +
  +                if (log.isDebugEnabled()) {
  +                    log.debug("s=" + s + " q1=" + q1 + " q2=" + q2 + " c=" + c);
  +                }
  +
  +                try {
  +                    //pull the current challenge and advance the start
  +                    challenge = authValue.substring(s, c).trim();
  +                    s = c+1;
  +
  +                    //find the blank and parse out the scheme
  +                    b = challenge.indexOf(' ');
  +                    scheme = (b > 0) 
  +                        ? challenge.substring(0, b).trim()
  +                        : challenge;
  +
  +                    //store the challenge keyed on the scheme
  +                    challengeMap.put(scheme.toLowerCase(), challenge);
  +                    if (log.isDebugEnabled()) {
  +                        log.debug(scheme.toLowerCase() + "=>" + challenge);
  +                    }
  +
  +                } catch (StringIndexOutOfBoundsException e) {
  +                    log.warn("Parsing authorization challenge'" + 
  +                            challenge + "' failed", e);
  +                }
  +
  +            }//end of while
  +            
  +        } catch (StringIndexOutOfBoundsException e) {
  +            log.warn("Parsing authorization header value'" + 
  +                    authValue + "' failed", e);
  +        }
  +
  +        return challengeMap;
  +    }
  +
  +
       /**
        * Add requisite authentication credentials to the given <i>method</i>
        * using the given the <i>challengeHeader</i>. Currently <b>Basic</b> and
  @@ -525,10 +613,9 @@
        * @see HttpMethod#addRequestHeader
        */
       private static boolean authenticate(HttpMethod method, HttpState state, 
  -                                        Header authenticateHeader, 
  -                                        String respHeader)
  -                                 throws HttpException, 
  -                                        UnsupportedOperationException {
  +            Header authenticateHeader, String respHeader)
  +    throws HttpException, UnsupportedOperationException {
  +
           log.trace("enter Authenticator.authenticate(HttpMethod, HttpState, "
                     + "Header, String)");
   
  @@ -569,67 +656,39 @@
                       return false;
                   }
               } else { //no challenge and no default creds so do nothing
  -
                   return false;
               }
           }
   
  -        if (log.isDebugEnabled()) {
  -            log.debug("Attempting to authenticate header: "
  -                      + authenticateHeader);
  -        }
  -
  -        // XXX: Get the challenge from the header
  -        String authenticateValue = authenticateHeader.getValue();
  -
  -        // TODO: Use regular expression pattern matching to parse the challenge
  -        //FIXME: This fails if the contents of a challenge contains a ','
  -        StringTokenizer challengeTok = new StringTokenizer(authenticateValue, 
  -                                                           ",");
  -        Map challengeMap = new Hashtable(7);
  -
  -        while (challengeTok.hasMoreTokens()) {
  -            // Parse the authentication scheme from the challenge
  -            String chall = challengeTok.nextToken();
  -            StringTokenizer authTok = new StringTokenizer(chall, " ");
  -            String authScheme = authTok.nextToken();
  -
  -
  -            // Store the challenge keyed on the lower case authenticaion scheme
  -            challengeMap.put(authScheme.toLowerCase(), chall);
  -        }
  +        //parse the authenticate header
  +        Map challengeMap = parseAuthenticateHeader(authenticateHeader);
   
  +        //determine the most secure request header to add
           Header requestHeader = null;
  -
  -        if (challengeMap.containsKey("digest")) {
  -            String challenge = (String) challengeMap.get("digest");
  -            String realm = parseRealmFromChallenge(challenge);
  -            requestHeader = Authenticator.digest(realm, method, state, 
  -                                                 respHeader);
  -        } else if (challengeMap.containsKey("ntlm")) {
  +        if (challengeMap.containsKey("ntlm")) {
               String challenge = (String) challengeMap.get("ntlm");
   			requestHeader = Authenticator.ntlm(challenge, method, state, respHeader);
  +        } else if (challengeMap.containsKey("digest")) {
  +            String challenge = (String) challengeMap.get("digest");
  +            String realm = parseRealmFromChallenge(challenge);
  +            requestHeader = Authenticator.digest(realm, method, state, \
respHeader);  } else if (challengeMap.containsKey("basic")) {
               String challenge = (String) challengeMap.get("basic");
               String realm = parseRealmFromChallenge(challenge);
               requestHeader = Authenticator.basic(realm, state, respHeader);
           } else if (challengeMap.size() == 0) {
               throw new HttpException("No authentication scheme found in '"
  -                                    + authenticateValue);
  +                    + authenticateHeader + "'");
           } else {
               throw new UnsupportedOperationException("Requested authentication "
  -                                                    + "scheme "
  -                                                    + challengeMap.keySet()
  -                                                    + " is unsupported");
  +                    + "scheme " + challengeMap.keySet() + " is unsupported");
           }
   
  -        //Add the header and return the result
  +        //Add the header if it has been created and return true 
           if (requestHeader != null) { // add the header
               method.addRequestHeader(requestHeader);
  -
               return true;
           } else { // don't add the header
  -
               return false;
           }
       }
  
  
  
  1.17      +34 -7     \
jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java
  
  Index: TestAuthenticator.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v
  retrieving revision 1.16
  retrieving revision 1.17
  diff -u -r1.16 -r1.17
  --- TestAuthenticator.java	26 Sep 2002 11:40:16 -0000	1.16
  +++ TestAuthenticator.java	30 Sep 2002 19:42:13 -0000	1.17
  @@ -206,7 +206,7 @@
   
       public void testBasicAuthentication() throws Exception {
           HttpState state = new HttpState();
  -        state.setCredentials("realm",new \
UsernamePasswordCredentials("username","password"));  +        \
state.setCredentials("realm", new \
                UsernamePasswordCredentials("username","password"));
           HttpMethod method = new SimpleHttpMethod(new \
Header("WWW-Authenticate","Basic realm=\"realm\""));  \
assertTrue(Authenticator.authenticate(method,state));  assertTrue(null != \
method.getRequestHeader("Authorization"));  @@ -506,6 +506,33 @@
           assertTrue(authValue.startsWith("Basic"));
       }
   
  +    public void testMultipleChallengeBasicLongRealm() throws Exception {
  +        HttpState state = new HttpState();
  +        state.setCredentials(null, new UsernamePasswordCredentials("name", \
"pass"));  +        HttpMethod method = new SimpleHttpMethod();
  +        SimpleHttpConnection conn = new SimpleHttpConnection();
  +        conn.addResponse(
  +            "HTTP/1.1 401 Unauthorized\r\n" + 
  +            "WWW-Authenticate: Unsupported\r\n" +
  +            "WWW-Authenticate: Basic realm=\"This site is protected.  We put this \
message into the realm string, against all reasonable rationale, so that users would \
see it in the authentication dialog generated by your browser.\"\r\n" +  +            \
"Connection: close\r\n" +  +            "Server: HttpClient Test/2.0\r\n"
  +                );
  +        conn.addResponse( 
  +            "HTTP/1.1 200 OK\r\n" +
  +            "Connection: close\r\n" +
  +            "Server: HttpClient Test/2.0\r\n"
  +                ); 
  +        method.execute(state, conn);
  +        Header authHeader = method.getRequestHeader("Authorization");
  +        assertNotNull(authHeader);
  +
  +        String authValue = authHeader.getValue();
  +        assertTrue(authValue.startsWith("Basic"));
  +    }
  +
  +
  +
   
       public void testMultipleChallengeDigest() throws Exception {
           HttpState state = new HttpState();
  @@ -514,7 +541,7 @@
           SimpleHttpConnection conn = new SimpleHttpConnection();
           conn.addResponse(
               "HTTP/1.1 401 Unauthorized\r\n" + 
  -            "WWW-Authenticate: NTLM\r\n" +
  +            "WWW-Authenticate: Unsupported\r\n" +
               "WWW-Authenticate: Digest realm=\"Protected\"\r\n" +
               "WWW-Authenticate: Basic realm=\"Protected\"\r\n" +
               "Connection: close\r\n" +
  @@ -569,7 +596,7 @@
               "HTTP/1.1 407 Proxy Authentication Required\r\n" + 
               "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" +
               "Proxy-Authenticate: Digest realm=\"Protected\"\r\n" +
  -            "Proxy-Authenticate: NTLM\r\n" +
  +            "Proxy-Authenticate: Unsupported\r\n" +
               "Connection: close\r\n" +
               "Server: HttpClient Test/2.0\r\n"
                   );
  
  
  

--
To unsubscribe, e-mail:   <mailto:commons-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:commons-dev-help@jakarta.apache.org>


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic