Author: cschneider
Date: Thu Dec 16 14:36:04 2010
New Revision: 1049988
URL: http://svn.apache.org/viewvc?rev=1049988&view=rev
Log:
CXF-3123 Some refactorings of the http auth code to make it easier to add new
auth mechanisms
Added:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
Modified:
cxf/trunk/rt/transports/common/ (props changed)
cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
Propchange: cxf/trunk/rt/transports/common/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Thu Dec 16 14:36:04 2010
@@ -0,0 +1 @@
+target
Modified:
cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
(original)
+++
cxf/trunk/rt/transports/http-osgi/src/main/java/org/apache/cxf/transport/http_osgi/OsgiServletController.java
Thu Dec 16 14:36:04 2010
@@ -41,7 +41,7 @@ import org.apache.cxf.transports.http.Qu
import org.apache.cxf.wsdl.http.AddressType;
public class OsgiServletController extends AbstractServletController {
- private static final Logger LOG = LogUtils.getL7dLogger(OsgiServlet.class);
+ private static final Logger LOG =
LogUtils.getL7dLogger(OsgiServletController.class);
public OsgiServletController(ServletConfig config,
DestinationRegistry destinationRegistry,
Modified:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
(original)
+++
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/DigestAuthSupplier.java
Thu Dec 16 14:36:04 2010
@@ -19,9 +19,6 @@
package org.apache.cxf.transport.http;
-import java.io.IOException;
-import java.io.StreamTokenizer;
-import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.MessageDigest;
@@ -53,8 +50,7 @@ public class DigestAuthSupplier extends
}
md5Helper = md;
}
-
-
+
/**
* {...@inheritdoc}
* With digest, the nonce could expire and thus a rechallenge will be
issued.
@@ -63,42 +59,14 @@ public class DigestAuthSupplier extends
public boolean requiresRequestCaching() {
return true;
}
-
- static Map<String, String> parseHeader(String fullHeader) {
-
- Map<String, String> map = new HashMap<String, String>();
- fullHeader = fullHeader.substring(7);
- try {
- StreamTokenizer tok = new StreamTokenizer(new
StringReader(fullHeader));
- tok.quoteChar('"');
- tok.quoteChar('\'');
- tok.whitespaceChars('=', '=');
- tok.whitespaceChars(',', ',');
-
- while (tok.nextToken() != StreamTokenizer.TT_EOF) {
- String key = tok.sval;
- if (tok.nextToken() == StreamTokenizer.TT_EOF) {
- map.put(key, null);
- return map;
- }
- String value = tok.sval;
- if (value.charAt(0) == '"') {
- value = value.substring(1, value.length() - 1);
- }
- map.put(key, value);
- }
- } catch (IOException ex) {
- //ignore
- }
- return map;
- }
-
+
@Override
public String getAuthorizationForRealm(HTTPConduit conduit, URL currentURL,
Message message,
String realm, String fullHeader) {
- if (fullHeader.startsWith("Digest ")) {
- Map<String, String> map = parseHeader(fullHeader);
+ HttpAuthHeader authHeader = new HttpAuthHeader(fullHeader);
+ if (authHeader.authTypeIsDigest()) {
+ Map<String, String> map = authHeader.getParams();
if ("auth".equals(map.get("qop"))
|| !map.containsKey("qop")) {
DigestInfo di = new DigestInfo();
@@ -160,6 +128,15 @@ public class DigestAuthSupplier extends
return null;
}
+ public String createCnonce() throws UnsupportedEncodingException {
+ String cnonce = Long.toString(System.currentTimeMillis());
+ byte[] bytes = cnonce.getBytes("US-ASCII");
+ synchronized (md5Helper) {
+ bytes = md5Helper.digest(bytes);
+ }
+ return encode(bytes);
+ }
+
class DigestInfo {
String qop;
String realm;
@@ -173,10 +150,7 @@ public class DigestAuthSupplier extends
synchronized String generateAuth(String uri, String username, String
password) {
try {
nc++;
- String ncstring = Integer.toString(nc);
- while (ncstring.length() < 8) {
- ncstring = "0" + ncstring;
- }
+ String ncstring = String.format("%08d", nc);
String cnonce = createCnonce();
String digAlg = algorithm;
@@ -188,14 +162,7 @@ public class DigestAuthSupplier extends
if ("MD5-sess".equalsIgnoreCase(algorithm)) {
algorithm = "MD5";
String tmp2 =
encode(digester.digest(a1.getBytes(charset)));
- StringBuilder tmp3 = new StringBuilder(
- tmp2.length() + nonce.length() + cnonce.length() +
2);
- tmp3.append(tmp2);
- tmp3.append(':');
- tmp3.append(nonce);
- tmp3.append(':');
- tmp3.append(cnonce);
- a1 = tmp3.toString();
+ a1 = tmp2 + ':' + nonce + ':' + cnonce;
}
String hasha1 = encode(digester.digest(a1.getBytes(charset)));
String a2 = method + ":" + uri;
@@ -207,36 +174,20 @@ public class DigestAuthSupplier extends
serverDigestValue = hasha1 + ":" + nonce + ":" + ncstring
+ ":" + cnonce + ":"
+ qop + ":" + hasha2;
}
- serverDigestValue =
encode(digester.digest(serverDigestValue.getBytes("US-ASCII")));
- StringBuilder builder = new StringBuilder("Digest ");
+ String response =
encode(digester.digest(serverDigestValue.getBytes("US-ASCII")));
+ Map<String, String> outParams = new HashMap<String, String>();
if (qop != null) {
- builder.append("qop=\"auth\", ");
- }
- builder.append("realm=\"")
- .append(realm);
-
- if (opaque != null) {
- builder.append("\", opaque=\"")
- .append(opaque);
+ outParams.put("qop", "auth");
}
-
- builder.append("\", nonce=\"")
- .append(nonce)
- .append("\", uri=\"")
- .append(uri)
- .append("\", username=\"")
- .append(username)
- .append("\", nc=")
- .append(ncstring)
- .append(", cnonce=\"")
- .append(cnonce)
- .append("\", response=\"")
- .append(serverDigestValue)
- .append("\"");
-
- return builder.toString();
- } catch (RuntimeException ex) {
- throw ex;
+ outParams.put("realm", realm);
+ outParams.put("opaque", opaque);
+ outParams.put("nonce", nonce);
+ outParams.put("uri", uri);
+ outParams.put("username", username);
+ outParams.put("nc", ncstring);
+ outParams.put("cnonce", cnonce);
+ outParams.put("response", response);
+ return new HttpAuthHeader(HttpAuthHeader.AUTH_TYPE_DIGEST,
outParams).getFullHeader();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
@@ -245,15 +196,6 @@ public class DigestAuthSupplier extends
}
- public String createCnonce() throws UnsupportedEncodingException {
- String cnonce = Long.toString(System.currentTimeMillis());
- byte[] bytes = cnonce.getBytes("US-ASCII");
- synchronized (md5Helper) {
- bytes = md5Helper.digest(bytes);
- }
- return encode(bytes);
- }
-
/**
* Encodes the 128 bit (16 bytes) MD5 digest into a 32 characters long
* <CODE>String</CODE> according to RFC 2617.
Modified:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
(original)
+++
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HTTPConduit.java
Thu Dec 16 14:36:04 2010
@@ -44,7 +44,6 @@ import javax.xml.namespace.QName;
import org.apache.cxf.Bus;
import org.apache.cxf.common.injection.NoJSR250Annotations;
import org.apache.cxf.common.logging.LogUtils;
-import org.apache.cxf.common.util.Base64Utility;
import org.apache.cxf.configuration.Configurable;
import org.apache.cxf.configuration.jsse.TLSClientParameters;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
@@ -755,7 +754,7 @@ public class HTTPConduit
&& authPolicy != null && authPolicy.isSetPassword()) {
passwd = authPolicy.getPassword();
}
- headers.setAuthorization(getBasicAuthHeader(userName, passwd));
+
headers.setAuthorization(HttpBasicAuthSupplier.getBasicAuthHeader(userName,
passwd));
} else if (authPolicy != null
&& authPolicy.isSetAuthorizationType()
&& authPolicy.isSetAuthorization()) {
@@ -769,7 +768,7 @@ public class HTTPConduit
if (proxyAuthPolicy.isSetPassword()) {
passwd = proxyAuthPolicy.getPassword();
}
- headers.setProxyAuthorization(getBasicAuthHeader(userName,
passwd));
+
headers.setProxyAuthorization(HttpBasicAuthSupplier.getBasicAuthHeader(userName,
passwd));
} else if (proxyAuthPolicy.isSetAuthorizationType()
&& proxyAuthPolicy.isSetAuthorization()) {
headers.setProxyAuthorization(proxyAuthPolicy.getAuthorizationType() + " "
@@ -943,12 +942,10 @@ public class HTTPConduit
switch(responseCode) {
case HttpURLConnection.HTTP_MOVED_PERM:
case HttpURLConnection.HTTP_MOVED_TEMP:
- connection =
- redirectRetransmit(origConnection, message, cachedStream);
+ connection = redirectRetransmit(origConnection, message,
cachedStream);
break;
case HttpURLConnection.HTTP_UNAUTHORIZED:
- connection =
- authorizationRetransmit(origConnection, message, cachedStream);
+ connection = authorizationRetransmit(origConnection, message,
cachedStream);
break;
default:
break;
@@ -979,89 +976,24 @@ public class HTTPConduit
if (!getClient(message).isAutoRedirect()) {
return connection;
}
-
- // We keep track of the redirections for redirect loop protection.
- Set<String> visitedURLs = getSetVisitedURLs(message);
-
- String lastURL = connection.getURL().toString();
- visitedURLs.add(lastURL);
-
- String newURL = extractLocation(connection.getHeaderFields());
+ URL newURL = extractLocation(connection.getHeaderFields());
+ detectRedirectLoop(getConduitName(), connection.getURL(), newURL,
message);
if (newURL != null) {
- // See if we are being redirected in a loop as best we can,
- // using string equality on URL.
- if (visitedURLs.contains(newURL)) {
- // We are in a redirect loop; -- bail
- if (LOG.isLoggable(Level.INFO)) {
- LOG.log(Level.INFO, "Redirect loop detected on Conduit \""
- + getConduitName()
- + "\" on '"
- + newURL
- + "'");
- }
- throw new IOException("Redirect loop detected on Conduit \""
- + getConduitName()
- + "\" on '"
- + newURL
- + "'");
- }
- // We are going to redirect.
- // Remove any Server Authentication Information for the previous
- // URL.
new Headers(message).removeAuthorizationHeaders();
- URL url = new URL(newURL);
-
// If user configured this Conduit with preemptive authorization
// it is meant to make it to the end. (Too bad that information
// went to every URL along the way, but that's what the user
// wants!
// TODO: Make this issue a security release note.
- setHeadersByAuthorizationPolicy(message, url);
-
- connection = retransmit(
- connection, url, message, cachedStream);
+ setHeadersByAuthorizationPolicy(message, newURL);
+ connection.disconnect();
+ return retransmit(newURL, message, cachedStream);
}
return connection;
}
/**
- * This function gets the Set of URLs on the message that is used to
- * keep track of the URLs that were used in getting authorization
- * information.
- *
- * @param message The message where the Set of URLs is stored.
- * @return The modifiable set of URLs that were visited.
- */
- private static Set<String> getSetAuthoriationURLs(Message message) {
- @SuppressWarnings("unchecked")
- Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
- if (authURLs == null) {
- authURLs = new HashSet<String>();
- message.put(KEY_AUTH_URLS, authURLs);
- }
- return authURLs;
- }
-
- /**
- * This function get the set of URLs on the message that is used to keep
- * track of the URLs that were visited in redirects.
- *
- * If it is not set on the message, an new empty set is stored.
- * @param message The message where the Set is stored.
- * @return The modifiable set of URLs that were visited.
- */
- private static Set<String> getSetVisitedURLs(Message message) {
- @SuppressWarnings("unchecked")
- Set<String> visitedURLs = (Set<String>) message.get(KEY_VISITED_URLS);
- if (visitedURLs == null) {
- visitedURLs = new HashSet<String>();
- message.put(KEY_VISITED_URLS, visitedURLs);
- }
- return visitedURLs;
- }
-
- /**
* This method performs a retransmit for authorization information.
*
* @param connection The currently active connection.
@@ -1076,12 +1008,11 @@ public class HTTPConduit
Message message,
CacheAndWriteOutputStream cachedStream
) throws IOException {
-
+ HttpAuthHeader authHeader = new
HttpAuthHeader(connection.getHeaderField("WWW-Authenticate"));
// If we don't have a dynamic supply of user pass, then
// we don't retransmit. We just die with a Http 401 response.
if (authSupplier == null) {
- String auth = connection.getHeaderField("WWW-Authenticate");
- if (auth.startsWith("Digest ")) {
+ if (authHeader.authTypeIsDigest()) {
authSupplier = new DigestAuthSupplier();
} else {
return connection;
@@ -1089,54 +1020,22 @@ public class HTTPConduit
}
URL currentURL = connection.getURL();
-
- String realm = extractAuthorizationRealm(connection.getHeaderFields());
-
+ String realm = authHeader.getRealm();
detectAuthorizationLoop(getConduitName(), message, currentURL, realm);
-
- String up =
+ String authorizationToken =
authSupplier.getAuthorizationForRealm(
- this, currentURL, message, realm,
connection.getHeaderField("WWW-Authenticate"));
-
- // No user pass combination. We give up.
- if (up == null) {
+ this, currentURL, message, realm, authHeader.getFullHeader());
+ if (authorizationToken == null) {
+ // authentication not possible => we give up
return connection;
}
- new Headers(message).setAuthorization(up);
-
- // also adding cookie headers when retransmitting in case of a "401
Unauthorized" response
+ new Headers(message).setAuthorization(authorizationToken);
cookies.writeToMessageHeaders(message);
-
- return retransmit(
- connection, currentURL, message, cachedStream);
+ connection.disconnect();
+ return retransmit(currentURL, message, cachedStream);
}
- private static void detectAuthorizationLoop(String conduitName, Message
message,
- URL currentURL, String realm) throws IOException {
- Set<String> authURLs = getSetAuthoriationURLs(message);
- // If we have been here (URL & Realm) before for this particular
message
- // retransmit, it means we have already supplied information
- // which must have been wrong, or we wouldn't be here again.
- // Otherwise, the server may be 401 looping us around the realms.
- if (authURLs.contains(currentURL.toString() + realm)) {
- String logMessage = "Authorization loop detected on Conduit \""
- + conduitName
- + "\" on URL \""
- + currentURL
- + "\" with realm \""
- + realm
- + "\"";
- if (LOG.isLoggable(Level.INFO)) {
- LOG.log(Level.INFO, logMessage);
- }
-
- throw new IOException(logMessage);
- }
- // Register that we have been here before we go.
- authURLs.add(currentURL.toString() + realm);
- }
-
/**
* This method retransmits the request.
*
@@ -1151,29 +1050,21 @@ public class HTTPConduit
* @throws IOException
*/
private HttpURLConnection retransmit(
- HttpURLConnection connection,
URL newURL,
Message message,
CacheAndWriteOutputStream stream
) throws IOException {
-
- // Disconnect the old, and in with the new.
- connection.disconnect();
-
-
HTTPClientPolicy cp = getClient(message);
- connection = createConnection(message, newURL);
+ HttpURLConnection connection = createConnection(message, newURL);
connection.setDoOutput(true);
- // TODO: using Message context to deceided HTTP send properties
+ // TODO: using Message context to decided HTTP send properties
connection.setConnectTimeout((int)cp.getConnectionTimeout());
connection.setReadTimeout((int)cp.getReceiveTimeout());
connection.setUseCaches(false);
connection.setInstanceFollowRedirects(false);
// If the HTTP_REQUEST_METHOD is not set, the default is "POST".
- String httpRequestMethod =
- (String)message.get(Message.HTTP_REQUEST_METHOD);
-
+ String httpRequestMethod =
(String)message.get(Message.HTTP_REQUEST_METHOD);
connection.setRequestMethod((null != httpRequestMethod) ?
httpRequestMethod : "POST");
message.put(KEY_HTTP_CONNECTION, connection);
@@ -1181,10 +1072,10 @@ public class HTTPConduit
if (stream != null) {
connection.setFixedLengthStreamingMode(stream.size());
}
-
+
// Need to set the headers before the trust decision
// because they are set before the connect().
- new Headers(message).setURLRequestHeaders(getConduitName());
+ new Headers(message).setProtocolHeadersInConnection(connection);
//
// This point is where the trust decision is made because the
@@ -1216,33 +1107,67 @@ public class HTTPConduit
return connection;
}
+ private static void detectAuthorizationLoop(String conduitName, Message
message,
+ URL currentURL, String realm)
throws IOException {
+ @SuppressWarnings("unchecked")
+ Set<String> authURLs = (Set<String>) message.get(KEY_AUTH_URLS);
+ if (authURLs == null) {
+ authURLs = new HashSet<String>();
+ message.put(KEY_AUTH_URLS, authURLs);
+ }
+ // If we have been here (URL & Realm) before for this particular
message
+ // retransmit, it means we have already supplied information
+ // which must have been wrong, or we wouldn't be here again.
+ // Otherwise, the server may be 401 looping us around the realms.
+ if (authURLs.contains(currentURL.toString() + realm)) {
+ String logMessage = "Authorization loop detected on Conduit \""
+ + conduitName
+ + "\" on URL \""
+ + currentURL
+ + "\" with realm \""
+ + realm
+ + "\"";
+ if (LOG.isLoggable(Level.INFO)) {
+ LOG.log(Level.INFO, logMessage);
+ }
+
+ throw new IOException(logMessage);
+ }
+ // Register that we have been here before we go.
+ authURLs.add(currentURL.toString() + realm);
+ }
+
/**
- * This function extracts the authorization realm from the
- * "WWW-Authenticate" Http response header.
+ * Tracks the visited urls in the message header KEY_VISITED_URLS.
+ * If a URL is to be visited twice an exception is thrown
*
- * @param headers The Http Response Headers
- * @return The realm, or null if it is non-existent.
+ * @param conduitName
+ * @param lastURL
+ * @param newURL
+ * @param message
+ * @throws IOException
*/
- private String extractAuthorizationRealm(
- Map<String, List<String>> headers
- ) {
- List<String> auth = headers.get("WWW-Authenticate");
- if (auth != null) {
- for (String a : auth) {
- int idx = a.indexOf("realm=");
- if (idx != -1) {
- a = a.substring(idx + 6);
- if (a.charAt(0) == '"') {
- a = a.substring(1, a.indexOf('"', 1));
- } else if (a.contains(",")) {
- a = a.substring(0, a.indexOf(','));
- }
- return a;
- }
- }
+ private static void detectRedirectLoop(String conduitName,
+ URL lastURL,
+ URL newURL,
+ Message message) throws IOException
{
+ @SuppressWarnings("unchecked")
+ Set<String> visitedURLs = (Set<String>) message.get(KEY_VISITED_URLS);
+ if (visitedURLs == null) {
+ visitedURLs = new HashSet<String>();
+ message.put(KEY_VISITED_URLS, visitedURLs);
}
- return null;
- }
+ visitedURLs.add(lastURL.toString());
+ if (newURL != null && visitedURLs.contains(newURL.toString())) {
+ // See if we are being redirected in a loop as best we can,
+ // using string equality on URL.
+ // We are in a redirect loop; -- bail
+ String msg = "Redirect loop detected on Conduit '"
+ + conduitName + "' on '" + newURL + "'";
+ LOG.log(Level.INFO, msg);
+ throw new IOException(msg);
+ }
+ }
/**
* This method extracts the value of the "Location" Http
@@ -1250,16 +1175,21 @@ public class HTTPConduit
*
* @param headers The Http response headers.
* @return The value of the "Location" header, null if non-existent.
+ * @throws MalformedURLException
*/
- private String extractLocation(
- Map<String, List<String>> headers
- ) {
+ private URL extractLocation(Map<String, List<String>> headers
+ ) throws MalformedURLException {
for (Map.Entry<String, List<String>> head : headers.entrySet()) {
if ("Location".equalsIgnoreCase(head.getKey())) {
List<String> locs = head.getValue();
if (locs != null && locs.size() > 0) {
- return locs.get(0);
+ String location = locs.get(0);
+ if (location != null) {
+ return new URL(location);
+ } else {
+ return null;
+ }
}
}
}
@@ -1267,29 +1197,6 @@ public class HTTPConduit
}
/**
- * This procedure sets the "Authorization" header with the
- * BasicAuth token, which is Base64 encoded.
- *
- * @param userid The user's id, which cannot be null.
- * @param password The password, it may be null.
- *
- * @param headers The headers map that gets the "Authorization" header
set.
- */
- private String getBasicAuthHeader(
- String userid,
- String password
- ) {
- String userpass = userid;
-
- userpass += ":";
- if (password != null) {
- userpass += password;
- }
- String token = Base64Utility.encode(userpass.getBytes());
- return "Basic " + token;
- }
-
- /**
* Wrapper output stream responsible for flushing headers and handling
* the incoming HTTP-level response (not necessarily the MEP response).
*/
@@ -1370,13 +1277,23 @@ public class HTTPConduit
throw e;
}
}
+
+ if (LOG.isLoggable(Level.FINE)) {
+ LOG.fine("Sending "
+ + connection.getRequestMethod()
+ + " Message with Headers to "
+ + connection.getURL()
+ + " Conduit :"
+ + conduitName
+ + "\nContent-Type: " + connection.getContentType() + "\n");
+ }
}
protected void handleHeadersTrustCaching() throws IOException {
// Need to set the headers before the trust decision
// because they are set before the connect().
- new Headers(outMessage).setURLRequestHeaders(conduitName);
-
+ new Headers(outMessage).setProtocolHeadersInConnection(connection);
+
//
// This point is where the trust decision is made because the
// Sun implementation of URLConnection will not let us
@@ -1499,37 +1416,24 @@ public class HTTPConduit
+ new String(cachedStream.getBytes()));
}
- HttpURLConnection oldcon = connection;
-
- HTTPClientPolicy policy = getClient(outMessage);
-
- // Default MaxRetransmits is -1 which means unlimited.
- int maxRetransmits = (policy == null)
- ? -1
- : policy.getMaxRetransmits();
-
- cookies.readFromConnection(oldcon);
-
- // MaxRetransmits of zero means zero.
- if (maxRetransmits == 0) {
- return;
- }
-
+
+ int maxRetransmits = getMaxRetransmits();
+ cookies.readFromConnection(connection);
int nretransmits = 0;
-
- connection = processRetransmit(connection, outMessage,
cachedStream);
-
- while (connection != oldcon) {
+ HttpURLConnection oldcon = null;
+ while (connection != oldcon && (maxRetransmits < 0 ||
nretransmits < maxRetransmits)) {
nretransmits++;
oldcon = connection;
-
- // A negative max means unlimited.
- if (maxRetransmits < 0 || nretransmits < maxRetransmits) {
- connection = processRetransmit(connection, outMessage,
cachedStream);
- }
+ connection = processRetransmit(connection, outMessage,
cachedStream);
}
}
}
+
+ private int getMaxRetransmits() {
+ HTTPClientPolicy policy = getClient(outMessage);
+ // Default MaxRetransmits is -1 which means unlimited.
+ return (policy == null) ? -1 : policy.getMaxRetransmits();
+ }
/**
* This procedure is called on the close of the output stream so
Modified:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
(original)
+++
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/Headers.java
Thu Dec 16 14:36:04 2010
@@ -246,18 +246,22 @@ public class Headers {
}
/**
- * Put the headers from Message.PROTOCOL_HEADERS headers into the URL
- * connection.
+ * Set content type and protocol headers (Message.PROTOCOL_HEADERS)
headers into the URL
+ * connection.
* Note, this does not mean they immediately get written to the output
* stream or the wire. They just just get set on the HTTP request.
*
- * @param message The outbound message.
+ * @param connection
* @throws IOException
*/
- public void setURLRequestHeaders(String conduitName) throws IOException {
- HttpURLConnection connection =
- (HttpURLConnection)message.get(KEY_HTTP_CONNECTION);
+ public void setProtocolHeadersInConnection(HttpURLConnection connection)
throws IOException {
+ String ct = determineContentType();
+ connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
+ transferProtocolHeadersToURLConnection(connection);
+ logProtocolHeaders(Level.FINE);
+ }
+ private String determineContentType() {
String ct = (String)message.get(Message.CONTENT_TYPE);
String enc = (String)message.get(Message.ENCODING);
@@ -272,20 +276,7 @@ public class Headers {
} else {
ct = "text/xml";
}
- connection.setRequestProperty(HttpHeaderHelper.CONTENT_TYPE, ct);
-
- if (LOG.isLoggable(Level.FINE)) {
- LOG.fine("Sending "
- + connection.getRequestMethod()
- + " Message with Headers to "
- + connection.getURL()
- + " Conduit :"
- + conduitName
- + "\nContent-Type: " + ct + "\n");
- new Headers(message).logProtocolHeaders(Level.FINE);
- }
-
- transferProtocolHeadersToURLConnection(connection);
+ return ct;
}
/**
Added:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java?rev=1049988&view=auto
==============================================================================
---
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
(added)
+++
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpAuthHeader.java
Thu Dec 16 14:36:04 2010
@@ -0,0 +1,139 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.transport.http;
+
+import java.io.IOException;
+import java.io.StreamTokenizer;
+import java.io.StringReader;
+import java.util.HashMap;
+import java.util.Map;
+
+public final class HttpAuthHeader {
+ public static final String AUTH_TYPE_BASIC = "Basic";
+ public static final String AUTH_TYPE_DIGEST = "Digest";
+ public static final String AUTH_TYPE_NEGOTIATE = "Negotiate";
+
+ private String fullHeader;
+ private String authType;
+ private String fullContent;
+ private Map<String, String> params;
+
+ public HttpAuthHeader(String fullHeader) {
+ this.fullHeader = fullHeader;
+ int spacePos = fullHeader.indexOf(' ');
+ if (spacePos == -1) {
+ this.authType = fullHeader;
+ } else {
+ this.authType = fullHeader.substring(0, spacePos);
+ this.fullContent = fullHeader.substring(spacePos + 1);
+ }
+ this.params = parseHeader();
+ }
+
+ public HttpAuthHeader(String authType, Map<String, String> params) {
+ this.authType = authType;
+ this.params = params;
+ this.fullContent = paramsToString();
+ this.fullHeader = authType + " " + fullContent;
+ }
+
+ private String paramsToString() {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String key : params.keySet()) {
+ String param = params.get(key);
+ if (param != null) {
+ if (!first) {
+ builder.append(", ");
+ }
+ builder.append(key + "=\"" + param + "\"");
+ first = false;
+ }
+ }
+ return builder.toString();
+ }
+
+ private Map<String, String> parseHeader() {
+ Map<String, String> map = new HashMap<String, String>();
+ try {
+ StreamTokenizer tok = new StreamTokenizer(new
StringReader(this.fullContent));
+ tok.quoteChar('"');
+ tok.quoteChar('\'');
+ tok.whitespaceChars('=', '=');
+ tok.whitespaceChars(',', ',');
+
+ while (tok.nextToken() != StreamTokenizer.TT_EOF) {
+ String key = tok.sval;
+ if (tok.nextToken() == StreamTokenizer.TT_EOF) {
+ map.put(key, null);
+ return map;
+ }
+ String value = tok.sval;
+ if (value.charAt(0) == '"') {
+ value = value.substring(1, value.length() - 1);
+ }
+ map.put(key, value);
+ }
+ } catch (IOException ex) {
+ // ignore can“t happen for StringReader
+ }
+ return map;
+ }
+
+ /**
+ * Extracts the authorization realm from the
+ * "WWW-Authenticate" Http response header.
+ *
+ * @param authenticate content of the WWW-Authenticate header
+ * @return The realm, or null if it is non-existent.
+ */
+ public String getRealm() {
+ Map<String, String> map = parseHeader();
+ return map.get("realm");
+ }
+
+ public boolean authTypeIsDigest() {
+ return AUTH_TYPE_DIGEST.equals(this.authType);
+ }
+
+ public boolean authTypeIsBasic() {
+ return AUTH_TYPE_BASIC.equals(this.authType);
+ }
+
+ public boolean authTypeIsNegotiate() {
+ return AUTH_TYPE_DIGEST.equals(this.authType);
+ }
+
+ public String getAuthType() {
+ return authType;
+ }
+
+ public String getFullContent() {
+ return fullContent;
+ }
+
+ public String getFullHeader() {
+ return this.fullHeader;
+ }
+
+ public Map<String, String> getParams() {
+ return params;
+ }
+
+}
Modified:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
(original)
+++
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/http/HttpBasicAuthSupplier.java
Thu Dec 16 14:36:04 2010
@@ -62,6 +62,11 @@ public abstract class HttpBasicAuthSuppl
protected HttpBasicAuthSupplier(String name) {
super(name);
}
+
+ public static String getBasicAuthHeader(String userName, String passwd) {
+ String userAndPass = userName + ":" + passwd;
+ return "Basic " + Base64Utility.encode(userAndPass.getBytes());
+ }
@Override
public String getAuthorizationForRealm(HTTPConduit conduit, URL
currentURL, Message message,
@@ -72,19 +77,18 @@ public abstract class HttpBasicAuthSuppl
message,
realm);
if (up != null) {
- String key = up.getUserid() + ":" + up.getPassword();
- return "Basic " + Base64Utility.encode(key.getBytes());
+ return HttpBasicAuthSupplier.getBasicAuthHeader(up.getUserid(),
up.getPassword());
}
return null;
}
+
@Override
public String getPreemptiveAuthorization(HTTPConduit conduit, URL
currentURL, Message message) {
UserPass up = getPreemptiveUserPass(conduit.getConduitName(),
currentURL,
message);
if (up != null) {
- String key = up.getUserid() + ":" + up.getPassword();
- return "Basic " + Base64Utility.encode(key.getBytes());
+ return HttpBasicAuthSupplier.getBasicAuthHeader(up.getUserid(),
up.getPassword());
}
return null;
}
Modified:
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
(original)
+++
cxf/trunk/rt/transports/http/src/main/java/org/apache/cxf/transport/servlet/AbstractServletController.java
Thu Dec 16 14:36:04 2010
@@ -40,7 +40,7 @@ import org.apache.cxf.transports.http.Qu
public abstract class AbstractServletController {
protected static final String DEFAULT_LISTINGS_CLASSIFIER = "/services";
- private static final Logger LOG =
LogUtils.getL7dLogger(ServletController.class);
+ private static final Logger LOG =
LogUtils.getL7dLogger(AbstractServletController.class);
protected boolean isHideServiceList;
protected boolean disableAddressUpdates;
Added:
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java?rev=1049988&view=auto
==============================================================================
---
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
(added)
+++
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/DigestAuthSupplierTest.java
Thu Dec 16 14:36:04 2010
@@ -0,0 +1,91 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.cxf.transport.http;
+
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Map;
+
+import org.apache.cxf.configuration.security.AuthorizationPolicy;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageImpl;
+import org.easymock.classextension.EasyMock;
+import org.easymock.classextension.IMocksControl;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class DigestAuthSupplierTest {
+
+ /**
+ * Tests that parseHeader correctly parses parameters that contain ==
+ *
+ * @throws Exception
+ */
+ @Test
+ public void testCXF2370() throws Exception {
+ String origNonce =
"MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==";
+ String fullHeader = "Digest realm=\"MyCompany realm.\", qop=\"auth\","
+ "nonce=\"" + origNonce
+ + "\"";
+ Map<String, String> map = new HttpAuthHeader(fullHeader).getParams();
+ assertEquals(origNonce, map.get("nonce"));
+ assertEquals("auth", map.get("qop"));
+ assertEquals("MyCompany realm.", map.get("realm"));
+ }
+
+ @Test
+ public void testEncode() throws MalformedURLException {
+ String origNonce =
"MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==";
+ String fullHeader = "Digest realm=\"MyCompany realm.\", qop=\"auth\","
+ "nonce=\"" + origNonce
+ + "\"";
+
+ /**
+ * Initialize DigestAuthSupplier that always uses the same cnonce so
we always
+ * get the same response
+ */
+ DigestAuthSupplier authSupplier = new DigestAuthSupplier() {
+
+ @Override
+ public String createCnonce() throws UnsupportedEncodingException {
+ return "27db039b76362f3d55da10652baee38c";
+ }
+
+ };
+ IMocksControl control = EasyMock.createControl();
+ HTTPConduit conduit = control.createMock(HTTPConduit.class);
+ AuthorizationPolicy authorizationPolicy = new AuthorizationPolicy();
+ authorizationPolicy.setUserName("testUser");
+ authorizationPolicy.setPassword("testPassword");
+
+
EasyMock.expect(conduit.getAuthorization()).andReturn(authorizationPolicy).atLeastOnce();
+ URL url = new URL("http://myserver");
+ Message message = new MessageImpl();
+ control.replay();
+ String authToken = authSupplier
+ .getAuthorizationForRealm(conduit, url, message, "myRealm",
fullHeader);
+ assertEquals("Digest response=\"28e616b6868f60aaf9b19bb5b172f076\", "
+ + "cnonce=\"27db039b76362f3d55da10652baee38c\", "
+ + "username=\"testUser\", nc=\"00000001\", "
+ +
"nonce=\"MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==\", "
+ + "realm=\"MyCompany realm.\", qop=\"auth\", uri=\"\"",
authToken);
+ control.verify();
+ }
+}
Modified:
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
URL:
http://svn.apache.org/viewvc/cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java?rev=1049988&r1=1049987&r2=1049988&view=diff
==============================================================================
---
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
(original)
+++
cxf/trunk/rt/transports/http/src/test/java/org/apache/cxf/transport/http/HTTPConduitTest.java
Thu Dec 16 14:36:04 2010
@@ -37,6 +37,7 @@ import org.apache.cxf.message.MessageImp
import org.apache.cxf.service.model.EndpointInfo;
import org.apache.cxf.ws.addressing.EndpointReferenceType;
import org.apache.cxf.wsdl.EndpointReferenceUtils;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -117,16 +118,7 @@ public class HTTPConduitTest extends Ass
"/bar/foo");
}
- @Test
- public void testCXF2370() throws Exception {
- String origNonce =
"MTI0ODg3OTc5NzE2OTplZGUyYTg0Yzk2NTFkY2YyNjc1Y2JjZjU2MTUzZmQyYw==";
- String fullHeader = "Digest realm=\"MyCompany realm.\", qop=\"auth\","
- + "nonce=\"" + origNonce + "\"";
- Map<String, String> map = DigestAuthSupplier.parseHeader(fullHeader);
- assertEquals(origNonce, map.get("nonce"));
- assertEquals("auth", map.get("qop"));
- assertEquals("MyCompany realm.", map.get("realm"));
- }
+
/**
* Verfies one of the tenents of our interface -- the Conduit sets up