? PreemptiveAuthorization.patch Index: java/org/apache/commons/httpclient/Authenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v retrieving revision 1.17 diff -u -r1.17 Authenticator.java --- java/org/apache/commons/httpclient/Authenticator.java 16 Jul 2002 13:52:57 -0000 1.17 +++ java/org/apache/commons/httpclient/Authenticator.java 16 Jul 2002 22:32:56 -0000 @@ -69,24 +69,49 @@ import java.security.MessageDigest; /** - *
Utility methods for HTTP authorization and authentication.
- *+ * Utility methods for HTTP authorization and authentication. + * * This class provides utility methods for generating * responses to HTTP www and proxy authentication challenges. - *
+ *+ * Preemptive authentication can be turned on by using the property value of + * #PREEMPTIVE_PROPERTY. If left unspecified, it has the default value of + * #PREEMPTIVE_DEFAULT. This configurable behaviour conforms to rcf2617: + *
+ * A client SHOULD assume that all paths at or deeper than the depth of + * the last symbolic element in the path field of the Request-URI also + * are within the protection space specified by the Basic realm value of + * the current challenge. A client MAY preemptively send the + * corresponding Authorization header with requests for resources in + * that space without receipt of another challenge from the server. + * Similarly, when a client sends a request to a proxy, it may reuse a + * userid and password in the Proxy-Authorization header field without + * receiving another challenge from the proxy server. + *+ * * @author Remy Maucherat * @author Rodney Waldhoff - * @author Jeff Dever + * @author Jeff Dever * @version $Revision: 1.17 $ $Date: 2002/07/16 13:52:57 $ */ class Authenticator { - + /** org.apache.commons.httpclient.Authenticator log. */ private static final Log log = LogSource.getInstance("org.apache.commons.httpclient.Authenticator"); /** Base 64 encoder. */ private static Base64 base64 = new Base64(); + /** + * The boolean property name to turn on preemptive authentication. + */ + static final String PREEMPTIVE_PROPERTY = "httpclient.authentication.preemptive"; + + /** + * The default property value for #PREEMPTIVE_PROPERTY. + */ + static final String PREEMPTIVE_DEFAULT = "false"; + /** * The www authenticate challange header */ @@ -107,138 +132,157 @@ */ public static final String PROXY_AUTH_RESP = "Proxy-Authorization"; + /** - * Add requisite authentication credentials to the given - * {@link HttpMethod}, if possible. + * Add requisite authentication credentials to the given method + * in the given state if possible. * - * @see HttpState#setCredentials(String, Credentials) HttpState.setCredentials - * @see #authenticate(HttpMethod,HttpState,Header,String) - * - * @param method a {@link HttpMethod} which requires authentication - * @param state a {@link HttpState} object providing {@link Credentials} + * @param method the HttpMethod which requires authentication + * @param state the HttpState object providing Credentials * * @throws HttpException when a parsing or other error occurs - * @throws UnsupportedOperationException when the given challenge type is not supported - * @return true if only if a response header was added + * @throws UnsupportedOperationException when the challenge type is not supported + * @return true if the Authenticate response header was added + * + * @see HttpState#setCredentials(String,Credentials) + * @see #authenticate(HttpMethod,HttpState,Header,String) */ static boolean authenticate(HttpMethod method, HttpState state) throws HttpException, UnsupportedOperationException { - log.debug("Authenticator.authenticate(HttpMethod, HttpState)"); + log.debug("enter Authenticator.authenticate(HttpMethod, HttpState)"); + Header challengeHeader = method.getResponseHeader(WWW_AUTH); - Header challengeHeader = method.getResponseHeader(WWW_AUTH); - if(null == challengeHeader) { return false; } - - return authenticate(method, state, challengeHeader, WWW_AUTH_RESP); - } + return authenticate(method, state, challengeHeader, WWW_AUTH_RESP); + } /** - * Add requisite proxy authentication credentials to the given - * {@link HttpMethod}, if possible. + * Add requisite proxy authentication credentials to the given method + * in the given state if possible. * - * @see HttpState#setProxyCredentials(String, Credentials) HttpState.setProxyCredentials - * @see #authenticate(HttpMethod,HttpState,Header,String) - * - * @param method a {@link HttpMethod} which requires authentication - * @param state a {@link HttpState} object providing {@link Credentials} + * @param method the HttpMethod which requires authentication + * @param state the HttpState object providing Credentials * * @throws HttpException when a parsing or other error occurs * @throws UnsupportedOperationException when the given challenge type is not supported - * @return true if only if a response header was added + * @return true if the Authenticate response header was added + * + * @see HttpState#setProxyCredentials(String,Credentials) + * @see #authenticate(HttpMethod,HttpState,Header,String) */ static boolean authenticateProxy(HttpMethod method, HttpState state) throws HttpException, UnsupportedOperationException { - log.debug("Authenticator.authenticateProxy(HttpMethod, HttpState)"); + log.debug("enter Authenticator.authenticateProxy(HttpMethod, HttpState)"); + Header challengeHeader = method.getResponseHeader(PROXY_AUTH); + + return authenticate(method, state, challengeHeader, PROXY_AUTH_RESP); + } - Header challengeHeader = method.getResponseHeader(PROXY_AUTH); - if (null == challengeHeader) { return false; } - return authenticate(method, state, challengeHeader, PROXY_AUTH_RESP); - } /** * Add requisite authentication credentials to the given - * {@link HttpMethod}, if possible, using the given response header + * method using the given the challengeHeader. * - * Currently only Basic authentication is supported. - * - * @param method the {@link HttpMethod http method} to add the authentication - * details to - * @param challengeHeader the header the web server created to challenge the - * credentials - * @param state a {@link HttpState} object providing {@link Credentials} + * Currently Basic and Digest authentication are supported. + * If the challengeHeader is null, the default authentication credentials + * will be sent. + * + * @param method the http method to add the authentication header to + * @param challengeHeader the header the web server created to challenge the credentials + * @param state the http state object providing {@link Credentials} * @param respHeader the response header to add (e.g. proxy or standard) * * @throws HttpException when an error occurs parsing the challenge * @throws UnsupportedOperationException when the given challenge type is not supported - * @return true if only if a response header was added - */ - private static boolean authenticate(HttpMethod method, HttpState state, Header challengeHeader, String respHeader) - throws HttpException, UnsupportedOperationException { + * @return true if a response header was added + * + * @see #basic + * @see #digest + * @see HttpMethod#addRequestHeader + */ + private static boolean authenticate(HttpMethod method, HttpState state, Header challengeHeader, String respHeader) throws HttpException, UnsupportedOperationException { + log.debug("enter Authenticator.authenticate(HttpMethod, HttpState, Header, String)"); + + //check the preemptive policy + //TODO: this needs to be a service from some configuration class + String preemptive_str = System.getProperties().getProperty( + PREEMPTIVE_PROPERTY, PREEMPTIVE_DEFAULT); + preemptive_str = preemptive_str.trim().toLowerCase(); + + if (! (preemptive_str.equals("true") || preemptive_str.equals("false")) ){ //property problem + log.warn("Configuration property " + PREEMPTIVE_PROPERTY + + " must be either true or false. Using default: " + PREEMPTIVE_DEFAULT); + preemptive_str = PREEMPTIVE_DEFAULT; + } + boolean preemptive = ("true".equals(preemptive_str)); + log.debug("Using preemptive authorization: " + preemptive); + + + //set the header preemptively if necessisary + if (challengeHeader == null){ + if (preemptive){ + Header requestHeader = Authenticator.basic(null, state, respHeader); + if(requestHeader != null) { // default credentials exist, add the header + log.debug("Preemptively sending default basic credentials"); + method.addRequestHeader(requestHeader); + return true; + } else { // no default credentials, don't add the header + log.debug("No default credentials to preemptively send"); + return false; + } + } else { + return false; + } + } + log.debug("challenge header is: " + challengeHeader); + + // Get the challenge from the header String challenge = challengeHeader.getValue(); - if(null == challenge) { return false; } + + // Parse the authentication scheme from the challenge + // TODO: Use regular expression pattern matching to parse the challenge int space = challenge.indexOf(' '); if(space < 0) { - throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected space"); + throw new HttpException("Authentication challenge \'" + challenge + "\'does not contain an authentication scheme"); } String authScheme = challenge.substring(0, space); - if ("basic".equalsIgnoreCase(authScheme)) { - // FIXME: Note that this won't work if there - // is more than one realm within - // the challenge - // FIXME: We could probably make it a bit - // more flexible in parsing as well. - - // parse the realm from the authentication challenge - if(challenge.length() < space + 1) { - throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm"); - } - String realmstr = challenge.substring(space+1,challenge.length()); - realmstr.trim(); - if(realmstr.length()<"realm=\"\"".length()) { - throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm"); - } - String realm = realmstr.substring("realm=\"".length(),realmstr.length()-1); - log.debug("Parsed realm \"" + realm + "\" from challenge \"" + challenge + "\"."); - Header header = Authenticator.basic(realm, state, respHeader); - if(null != header) { - method.addRequestHeader(header); - return true; - } else { - return false; - } - } else if ("digest".equalsIgnoreCase(authScheme)) { - // FIXME: Note that this won't work if there - // is more than one realm within - // the challenge - // FIXME: We could probably make it a bit - // more flexible in parsing as well. - - // parse the realm from the authentication challenge - if(challenge.length() < space + 1) { - throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm"); - } - String realmstr = challenge.substring(space+1,challenge.length()); - realmstr.trim(); - if(realmstr.length()<"realm=\"\"".length()) { - throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm"); - } - String realm = realmstr.substring("realm=\"".length(),realmstr.length()-1); - log.debug("Parsed realm \"" + realm + "\" from challenge \"" + challenge + "\"."); - Header header = Authenticator.digest(realm, method, state, respHeader); - if(null != header) { - method.addRequestHeader(header); - return true; - } else { - return false; - } - } else { + // Parse the realm from the authentication challenge + // FIXME: Note that this won't work if there is more than one realm within the challenge + if (challenge.length() < space + 1) { + throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm"); + } + String realmstr = challenge.substring(space+1, challenge.length()); + realmstr.trim(); + if (realmstr.length() < "realm=\"\"".length()) { + throw new HttpException("Unable to parse authentication challenge \"" + challenge + "\", expected realm"); + } + String realm = realmstr.substring("realm=\"".length(), realmstr.length()-1); + log.debug("Parsed realm \"" + realm + "\" from challenge \"" + challenge + "\"."); + + // Check for the authentication type, and add header if necessisary + Header requestHeader = null; + if ("basic".equalsIgnoreCase(authScheme)) { // Basic authentication + requestHeader = Authenticator.basic(realm, state, respHeader); + + } else if ("digest".equalsIgnoreCase(authScheme)) { // Digest authentication + requestHeader = Authenticator.digest(realm, method, state, respHeader); + + } else { // unrecognized authentication throw new UnsupportedOperationException("Authentication type \"" + authScheme + "\" is not recognized."); } - } + + if(requestHeader != null) { // add the header + method.addRequestHeader(requestHeader); + return true; + } else { // don't add the header + return false; + } + } + /** * Create a Basic Authorization header for the given @@ -254,7 +298,8 @@ * @throws HttpException when no matching credentials are available */ static Header basic(String realm, HttpState state, String respHeader) throws HttpException { - log.debug("Authenticator.basic(String,HttpState)"); + + log.debug("enter Authenticator.basic(String, HttpState)"); boolean proxy = PROXY_AUTH_RESP.equals(respHeader); UsernamePasswordCredentials cred = null; try { @@ -264,20 +309,9 @@ } catch(ClassCastException e) { throw new HttpException("UsernamePasswordCredentials required for Basic authentication."); } + if(null == cred) { - if(log.isInfoEnabled()) { - log.info("No credentials found for realm \"" + realm + "\", attempting to use default credentials."); - } - try { - cred = (UsernamePasswordCredentials)( proxy ? - state.getProxyCredentials(null) : - state.getCredentials(null)); - } catch(ClassCastException e) { - throw new HttpException("UsernamePasswordCredentials required for Basic authentication."); - } - } - if(null == cred) { - throw new HttpException("No credentials available for the Basic authentication realm \"" + realm + "\"/"); + throw new HttpException("No credentials available for the Basic authentication realm \'" + realm + "\'"); } else { return new Header(respHeader, Authenticator.basic(cred)); } @@ -374,9 +408,9 @@ MessageDigest md5Helper; try { - md5Helper = MessageDigest.getInstance(digAlg); + md5Helper = MessageDigest.getInstance(digAlg); } catch (Exception e) { - System.out.println("ERROR! Unsupported algorithm in HTTP Digest authentication: "+digAlg); + log.error("ERROR! Unsupported algorithm in HTTP Digest authentication: "+digAlg); HttpException he = new HttpException("Unsupported algorithm in HTTP Digest authentication: "+digAlg); throw he; } @@ -389,7 +423,7 @@ String serverDigestValue; if (qop==null) serverDigestValue = md5a1 + ":" + nonce + ":" + md5a2; else serverDigestValue = md5a1 + ":" + nonce + ":" + nc + ":" + - cnonce + ":" + qop + ":" + md5a2; + cnonce + ":" + qop + ":" + md5a2; String serverDigest = encode(md5Helper.digest(serverDigestValue.getBytes())); return serverDigest; } @@ -406,9 +440,9 @@ String digAlg = "MD5"; MessageDigest md5Helper; try { - md5Helper = MessageDigest.getInstance(digAlg); + md5Helper = MessageDigest.getInstance(digAlg); } catch (Exception e) { - System.out.println("ERROR! Unsupported algorithm in HTTP Digest authentication: "+digAlg); + log.error("ERROR! Unsupported algorithm in HTTP Digest authentication: "+digAlg); HttpException he = new HttpException("Unsupported algorithm in HTTP Digest authentication: "+digAlg); throw he; } @@ -442,15 +476,15 @@ String algorithm = "MD5"; //we only support MD5 sb.append("username=\""+uname+"\"") - .append(", realm=\""+realm+"\"") - .append(", nonce=\""+nonce+"\"") - .append(", uri=\""+uri+"\"") - .append((qop==null?"":", qop=\""+qop+"\"")) - .append(", algorithm=\""+algorithm+"\"") - .append((qop==null?"":", nc="+nc)) - .append((qop==null?"":", cnonce=\""+cnonce+"\"")) - .append(", response=\""+response+"\"") - .append(", opaque=\""+opaque+"\""); + .append(", realm=\""+realm+"\"") + .append(", nonce=\""+nonce+"\"") + .append(", uri=\""+uri+"\"") + .append((qop==null?"":", qop=\""+qop+"\"")) + .append(", algorithm=\""+algorithm+"\"") + .append((qop==null?"":", nc="+nc)) + .append((qop==null?"":", cnonce=\""+cnonce+"\"")) + .append(", response=\""+response+"\"") + .append(", opaque=\""+opaque+"\""); return sb.toString(); } @@ -467,15 +501,15 @@ *
str
*/
private static String removeQuotes(String str) {
- if (str == null)
- return null;
+ if (str == null)
+ return null;
- int fqpos = str.indexOf("\"")+1;
- int lqpos = str.lastIndexOf("\"");
- if (fqpos > 0 && lqpos > fqpos)
- return str.substring(fqpos,lqpos);
- else
- return str;
+ int fqpos = str.indexOf("\"")+1;
+ int lqpos = str.lastIndexOf("\"");
+ if (fqpos > 0 && lqpos > fqpos)
+ return str.substring(fqpos,lqpos);
+ else
+ return str;
}
/**
@@ -488,19 +522,19 @@
*/
private static String encode( byte[] binaryData ) {
- if (binaryData.length != 16)
- return null;
+ if (binaryData.length != 16)
+ return null;
- char[] buffer = new char[32];
+ char[] buffer = new char[32];
- for (int i=0; i<16; i++) {
- int low = (int) (binaryData[i] & 0x0f);
- int high = (int) ((binaryData[i] & 0xf0) >> 4);
- buffer[i*2] = hexadecimal[high];
- buffer[i*2 + 1] = hexadecimal[low];
- }
+ for (int i=0; i<16; i++) {
+ int low = (int) (binaryData[i] & 0x0f);
+ int high = (int) ((binaryData[i] & 0xf0) >> 4);
+ buffer[i*2] = hexadecimal[high];
+ buffer[i*2 + 1] = hexadecimal[low];
+ }
- return new String(buffer);
+ return new String(buffer);
}
/**
@@ -509,9 +543,8 @@
*
* @see #encode(byte[])
*/
- private static final char[] hexadecimal =
- {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
- 'a', 'b', 'c', 'd', 'e', 'f'};
+ private static final char[] hexadecimal =
+ { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
/**
* Processes the www-authenticate HTTP header received from the server that
@@ -549,9 +582,12 @@
* entry is placed (only if it has "xxx=yyy" format).
*/
private static void processDigestToken(String token, Hashtable ht) {
- int eqpos = token.indexOf("=");
+ int eqpos = token.indexOf("=");
- if (eqpos > 0 && eqpos < token.length()-1)
- ht.put(token.substring(0,eqpos).trim(),token.substring(eqpos+1).trim());
+ if (eqpos > 0 && eqpos < token.length()-1)
+ ht.put(token.substring(0,eqpos).trim(),token.substring(eqpos+1).trim());
}
+
+
+
}
Index: java/org/apache/commons/httpclient/HttpMethodBase.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v
retrieving revision 1.31
diff -u -r1.31 HttpMethodBase.java
--- java/org/apache/commons/httpclient/HttpMethodBase.java 13 Jul 2002 09:06:29 -0000 1.31
+++ java/org/apache/commons/httpclient/HttpMethodBase.java 16 Jul 2002 22:32:57 -0000
@@ -112,7 +112,7 @@
* @author Rodney Waldhoff
* @author Sean C. Sullivan
* @author dIon Gillard
- * @author Jeff Dever
+ * @author Jeff Dever
* @version $Revision: 1.31 $ $Date: 2002/07/13 09:06:29 $
*/
public abstract class HttpMethodBase implements HttpMethod {
@@ -233,10 +233,14 @@
}
/**
- * Add the specified request header, NOT overwriting any
- * previous value.
+ * Add the specified request header.
+ *
+ * If a header of the same name already exists, the new value will be
+ * appended onto the the existing value list.
+ * A header value of null will be ignored.
* Note that header-name matching is case insensitive.
- * @param header the header
+ *
+ * @param header the header to add to the request
*/
public void addRequestHeader(Header header) {
// "It must be possible to combine the multiple header fields into
@@ -244,6 +248,13 @@
// semantics of the message, by appending each subsequent field-value
// to the first, each separated by a comma."
// - HTTP/1.0 (4.3)
+ log.debug("HttpMethodBase.addRequestHeader(Header)");
+
+ if (header == null){
+ log.debug("null header value ignored");
+ }
+
+ // Preserve the original header if it exists
Header orig = (Header)(requestHeaders.get(header.getName().toLowerCase()));
if (null == orig) {
orig = header;
@@ -252,7 +263,7 @@
", " +
(null == header.getValue() ? "" : header.getValue()));
}
- requestHeaders.put(orig.getName().toLowerCase(),orig);
+ requestHeaders.put(orig.getName().toLowerCase(), orig);
}
/**
@@ -431,7 +442,7 @@
*/
public int execute(HttpState state, HttpConnection connection) throws HttpException, IOException {
if (log.isDebugEnabled()) {
- log.debug("HttpMethodBase.execute(HttpState,HttpConnection)");
+ log.debug("enter HttpMethodBase.execute(HttpState, HttpConnection)");
}
if (null == state) {
@@ -450,6 +461,9 @@
throw new HttpException("Not valid");
}
+ //pre-emptively add the authorization header, if required.
+ Authenticator.authenticate(this, state);
+
Set visited = new HashSet();
Set realms = new HashSet();
int retryCount = 0;
@@ -460,6 +474,7 @@
log.debug("HttpMethodBase.execute(): looping.");
}
+
try{
if (!connection.isOpen()) {
if (log.isDebugEnabled()) {
@@ -520,9 +535,10 @@
realms.add(pathAndCreds);
}
+ removeRequestHeader(Authenticator.WWW_AUTH_RESP); //remove preemptively header
boolean authenticated = false;
try {
- authenticated = Authenticator.authenticate(this,state);
+ authenticated = Authenticator.authenticate(this, state);
} catch (HttpException httpe) {
log.warn(httpe.getMessage());
} catch (UnsupportedOperationException uoe) {
Index: test/org/apache/commons/httpclient/TestAuthenticator.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v
retrieving revision 1.9
diff -u -r1.9 TestAuthenticator.java
--- test/org/apache/commons/httpclient/TestAuthenticator.java 16 Jul 2002 13:52:57 -0000 1.9
+++ test/org/apache/commons/httpclient/TestAuthenticator.java 16 Jul 2002 22:32:58 -0000
@@ -73,7 +73,7 @@
* Unit tests for {@link Authenticator}.
*
* @author Rodney Waldhoff
- * @author Jeff Dever
+ * @author Jeff Dever
* @version $Id: TestAuthenticator.java,v 1.9 2002/07/16 13:52:57 jsdever Exp $
*/
public class TestAuthenticator extends TestCase {
@@ -158,7 +158,8 @@
public void testBasicAuthenticationWithNoChallenge() throws Exception {
HttpState state = new HttpState();
HttpMethod method = new SimpleHttpMethod(null);
- assertTrue(false == Authenticator.authenticate(method,state));
+
+ assertEquals(false, Authenticator.authenticate(method,state));
}
public void testBasicAuthenticationWithNullHttpState() throws Exception {
@@ -171,18 +172,6 @@
}
}
- public void testDigestAuthenticationScheme() throws Exception {
- HttpState state = new HttpState();
- state.setCredentials("realm1",new UsernamePasswordCredentials("username","password"));
- {
- HttpMethod method = new SimpleHttpMethod(new Header("WWW-Authenticate","Basic realm=\"realm1\""));
- assertTrue(Authenticator.authenticate(method,state));
- assertTrue(null != method.getRequestHeader("Authorization"));
- //TODO: test for correctness
- }
-
- }
-
public void testInvalidAuthenticationScheme() throws Exception {
HttpState state = new HttpState();
state.setCredentials(null,new UsernamePasswordCredentials("username","password"));
@@ -246,6 +235,40 @@
}
}
+ public void testPreemptiveAuthorizationDefault() throws Exception {
+ HttpState state = new HttpState();
+ HttpMethod method = new SimpleHttpMethod(null);
+ state.setCredentials(null, new UsernamePasswordCredentials("username","password"));
+
+ assertTrue(! Authenticator.authenticate(method,state));
+ assertTrue(null == method.getRequestHeader("Authorization"));
+ }
+
+ public void testPreemptiveAuthorizationTrue() throws Exception {
+ HttpState state = new HttpState();
+ HttpMethod method = new SimpleHttpMethod(null);
+ state.setCredentials(null, new UsernamePasswordCredentials("username","password"));
+
+ System.getProperties().setProperty(Authenticator.PREEMPTIVE_PROPERTY, "true");
+ assertTrue(Authenticator.authenticate(method,state));
+ assertTrue(null != method.getRequestHeader("Authorization"));
+ String expected = "Basic " + new String(Base64.encode("username:password".getBytes()));
+ assertEquals(expected, method.getRequestHeader("Authorization").getValue());
+ }
+
+ public void testPreemptiveAuthorizationFalse() throws Exception {
+ HttpState state = new HttpState();
+ HttpMethod method = new SimpleHttpMethod(null);
+
+ System.getProperties().setProperty(Authenticator.PREEMPTIVE_PROPERTY, "false");
+ state.setCredentials(null, new UsernamePasswordCredentials("username","password"));
+ assertTrue(! Authenticator.authenticate(method,state));
+ assertTrue(null == method.getRequestHeader("Authorization"));
+ }
+
+
+
+
// --------------------------------- Test Methods for Digest Authentication
public void testDigestAuthenticationWithNoCreds() {
@@ -359,4 +382,6 @@
String digest = Authenticator.createDigest(cred.getUserName(),cred.getPassword(), table);
assertEquals(response, digest);
}
+
+
}