[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