HTTPClient.DefaultAuthHandler Maven / Gradle / Ivy
Show all versions of grinder-httpclient Show documentation
/*
* @(#)DefaultAuthHandler.java 0.3-3 06/05/2001
*
* This file is part of the HTTPClient package
* Copyright (C) 1996-2001 Ronald Tschalär
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307, USA
*
* For questions, suggestions, bug-reports, enhancement-requests etc.
* I may be contacted at:
*
* [email protected]
*
* The HTTPClient's home page is located at:
*
* http://www.innovation.ch/java/HTTPClient/
*
*/
package HTTPClient;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.DataInputStream;
import java.io.InputStreamReader;
import java.util.Vector;
import java.util.StringTokenizer;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.Label;
import java.awt.Button;
import java.awt.Dimension;
import java.awt.TextField;
import java.awt.GridLayout;
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.GridBagConstraints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
/**
* This class is the default authorization handler. It currently handles the
* authentication schemes "Basic", "Digest", and "SOCKS5" (used for the
* SocksClient and not part of HTTP per se).
*
* By default, when a username and password is required, this handler throws
* up a message box requesting the desired info. However, applications can
* {@link #setAuthorizationPrompter(HTTPClient.AuthorizationPrompter) set their
* own authorization prompter} if desired.
*
*
Note: all methods except for
* setAuthorizationPrompter are meant to be invoked by the
* AuthorizationModule only, i.e. should not be invoked by the application
* (those methods are only public because implementing the
* AuthorizationHandler interface requires them to be).
*
* @version 0.3-3 06/05/2001
* @author Ronald Tschalär
* @since V0.2
*/
public class DefaultAuthHandler implements AuthorizationHandler, GlobalConstants
{
private static final byte[] NUL = new byte[0];
private static final int DI_A1 = 0;
private static final int DI_A1S = 1;
private static final int DI_QOP = 2;
private static byte[] digest_secret = null;
private static AuthorizationPrompter prompter = null;
private static boolean prompterSet = false;
/**
* For Digest authentication we need to set the uri, response and
* opaque parameters. For "Basic" and "SOCKS5" nothing is done.
*/
public AuthorizationInfo fixupAuthInfo(AuthorizationInfo info,
RoRequest req,
AuthorizationInfo challenge,
RoResponse resp)
throws AuthSchemeNotImplException
{
// nothing to do for Basic and SOCKS5 schemes
if (info.getScheme().equalsIgnoreCase("Basic") ||
info.getScheme().equalsIgnoreCase("SOCKS5"))
return info;
else if (!info.getScheme().equalsIgnoreCase("Digest"))
throw new AuthSchemeNotImplException(info.getScheme());
if (Log.isEnabled(Log.AUTH))
Log.write(Log.AUTH, "Auth: fixing up Authorization for host " +
info.getHost()+":"+info.getPort() +
"; scheme: " + info.getScheme() +
"; realm: " + info.getRealm());
return digest_fixup(info, req, challenge, resp);
}
/**
* returns the requested authorization, or null if none was given.
*
* @param challenge the parsed challenge from the server.
* @param req the request which solicited this response
* @param resp the full response received
* @return a structure containing the necessary authorization info,
* or null
* @exception AuthSchemeNotImplException if the authentication scheme
* in the challenge cannot be handled.
*/
public AuthorizationInfo getAuthorization(AuthorizationInfo challenge,
RoRequest req, RoResponse resp)
throws AuthSchemeNotImplException, IOException
{
AuthorizationInfo cred;
if (Log.isEnabled(Log.AUTH))
Log.write(Log.AUTH, "Auth: Requesting Authorization for host " +
challenge.getHost()+":"+challenge.getPort() +
"; scheme: " + challenge.getScheme() +
"; realm: " + challenge.getRealm());
// we only handle Basic, Digest and SOCKS5 authentication
if (!challenge.getScheme().equalsIgnoreCase("Basic") &&
!challenge.getScheme().equalsIgnoreCase("Digest") &&
!challenge.getScheme().equalsIgnoreCase("SOCKS5"))
throw new AuthSchemeNotImplException(challenge.getScheme());
// For digest authentication, check if stale is set
if (challenge.getScheme().equalsIgnoreCase("Digest"))
{
cred = digest_check_stale(challenge, req, resp);
if (cred != null)
return cred;
}
// Ask the user for username/password
NVPair answer;
synchronized (getClass())
{
if (!req.allowUI() || prompterSet && prompter == null)
return null;
if (prompter == null)
setDefaultPrompter();
answer = prompter.getUsernamePassword(challenge,
resp.getStatusCode() == 407);
}
if (answer == null)
return null;
// Now process the username/password
if (challenge.getScheme().equalsIgnoreCase("basic"))
{
cred = new AuthorizationInfo(challenge.getHost(),
challenge.getPort(),
challenge.getScheme(),
challenge.getRealm(),
Codecs.base64Encode(
answer.getName() + ":" +
answer.getValue()));
}
else if (challenge.getScheme().equalsIgnoreCase("Digest"))
{
cred = digest_gen_auth_info(challenge.getHost(),
challenge.getPort(),
challenge.getRealm(), answer.getName(),
answer.getValue(),
req.getConnection().getContext());
cred = digest_fixup(cred, req, challenge, null);
}
else // SOCKS5
{
NVPair[] upwd = { answer };
cred = new AuthorizationInfo(challenge.getHost(),
challenge.getPort(),
challenge.getScheme(),
challenge.getRealm(),
upwd, null);
}
// try to get rid of any unencoded passwords in memory
answer = null;
System.gc();
// Done
Log.write(Log.AUTH, "Auth: Got Authorization");
return cred;
}
/**
* We handle the "Authentication-Info" and "Proxy-Authentication-Info"
* headers here.
*/
public void handleAuthHeaders(Response resp, RoRequest req,
AuthorizationInfo prev,
AuthorizationInfo prxy)
throws IOException
{
String auth_info = resp.getHeader("Authentication-Info");
String prxy_info = resp.getHeader("Proxy-Authentication-Info");
if (auth_info == null && prev != null &&
hasParam(prev.getParams(), "qop", "auth-int"))
auth_info = "";
if (prxy_info == null && prxy != null &&
hasParam(prxy.getParams(), "qop", "auth-int"))
prxy_info = "";
try
{
handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req,
true);
handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp,
req, true);
}
catch (ParseException pe)
{ throw new IOException(pe.toString()); }
}
/**
* We handle the "Authentication-Info" and "Proxy-Authentication-Info"
* trailers here.
*/
public void handleAuthTrailers(Response resp, RoRequest req,
AuthorizationInfo prev,
AuthorizationInfo prxy)
throws IOException
{
String auth_info = resp.getTrailer("Authentication-Info");
String prxy_info = resp.getTrailer("Proxy-Authentication-Info");
try
{
handleAuthInfo(auth_info, "Authentication-Info", prev, resp, req,
false);
handleAuthInfo(prxy_info, "Proxy-Authentication-Info", prxy, resp,
req, false);
}
catch (ParseException pe)
{ throw new IOException(pe.toString()); }
}
private static void handleAuthInfo(String auth_info, String hdr_name,
AuthorizationInfo prev, Response resp,
RoRequest req, boolean in_headers)
throws ParseException, IOException
{
if (auth_info == null) return;
Vector pai = Util.parseHeader(auth_info);
HttpHeaderElement elem;
if (handle_nextnonce(prev, req,
elem = Util.getElement(pai, "nextnonce")))
pai.removeElement(elem);
if (handle_discard(prev, req, elem = Util.getElement(pai, "discard")))
pai.removeElement(elem);
if (in_headers)
{
HttpHeaderElement qop = null;
if (pai != null &&
(qop = Util.getElement(pai, "qop")) != null &&
qop.getValue() != null)
{
handle_rspauth(prev, resp, req, pai, hdr_name);
}
else if (prev != null &&
(Util.hasToken(resp.getHeader("Trailer"), hdr_name) &&
hasParam(prev.getParams(), "qop", null) ||
hasParam(prev.getParams(), "qop", "auth-int")))
{
handle_rspauth(prev, resp, req, null, hdr_name);
}
else if ((pai != null && qop == null &&
pai.contains(new HttpHeaderElement("digest"))) ||
(Util.hasToken(resp.getHeader("Trailer"), hdr_name) &&
prev != null &&
!hasParam(prev.getParams(), "qop", null)))
{
handle_digest(prev, resp, req, hdr_name);
}
}
if (pai.size() > 0)
resp.setHeader(hdr_name, Util.assembleHeader(pai));
else
resp.deleteHeader(hdr_name);
}
private static final boolean hasParam(NVPair[] params, String name,
String val)
{
for (int idx=0; idx> 8) & 0xFF);
time[2] = (byte) ((l_time >> 16) & 0xFF);
time[3] = (byte) ((l_time >> 24) & 0xFF);
time[4] = (byte) ((l_time >> 32) & 0xFF);
time[5] = (byte) ((l_time >> 40) & 0xFF);
time[6] = (byte) ((l_time >> 48) & 0xFF);
time[7] = (byte) ((l_time >> 56) & 0xFF);
params[cnonce] =
new NVPair("cnonce", MD5.hexDigest(digest_secret, time));
}
// select qop option
if (ch_qop != -1)
{
if (qop == -1)
{
params = Util.resizeArray(params, params.length+1);
qop = params.length-1;
}
extra[DI_QOP] = ch_params[ch_qop].getValue();
// select qop option
String[] qops = splitList(extra[DI_QOP], ",");
String p = null;
for (int idx=0; idx= HTTP_1_1))
{
p = "auth-int";
break;
}
if (qops[idx].equalsIgnoreCase("auth"))
p = "auth";
}
if (p == null)
{
for (int idx=0; idxnum bytes of random data.
*
* @param num the number of bytes to generate
* @return a byte array of random data
*/
private static byte[] gen_random_bytes(int num)
{
// first try /dev/random
try
{
FileInputStream rnd = new FileInputStream("/dev/random");
DataInputStream din = new DataInputStream(rnd);
byte[] data = new byte[num];
din.readFully(data);
try { din.close(); } catch (IOException ioe) { }
return data;
}
catch (Throwable t)
{ }
/* This is probably a much better generator, but it can be awfully
* slow (~ 6 secs / byte on my old LX)
*/
//return new java.security.SecureRandom().getSeed(num);
/* this is faster, but needs to be done better... */
byte[] data = new byte[num];
try
{
long fm = Runtime.getRuntime().freeMemory();
data[0] = (byte) (fm & 0xFF);
data[1] = (byte) ((fm >> 8) & 0xFF);
int h = data.hashCode();
data[2] = (byte) (h & 0xFF);
data[3] = (byte) ((h >> 8) & 0xFF);
data[4] = (byte) ((h >> 16) & 0xFF);
data[5] = (byte) ((h >> 24) & 0xFF);
long time = System.currentTimeMillis();
data[6] = (byte) (time & 0xFF);
data[7] = (byte) ((time >> 8) & 0xFF);
}
catch (ArrayIndexOutOfBoundsException aioobe)
{ }
return data;
}
/**
* Return the value of the first NVPair whose name matches the key
* using a case-insensitive search.
*
* @param list an array of NVPair's
* @param key the key to search for
* @return the value of the NVPair with that key, or null if not
* found.
*/
private final static String getValue(NVPair[] list, String key)
{
int len = list.length;
for (int idx=0; idx> 4) & 15, 16));
str.append(Character.forDigit(buf[idx] & 15, 16));
str.append(':');
}
str.setLength(str.length()-1);
return str.toString();
}
static final byte[] unHex(String hex)
{
byte[] digest = new byte[hex.length()/2];
for (int idx=0; idx= 0 || os.indexOf("SunOS") >= 0 ||
os.indexOf("Solaris") >= 0 || os.indexOf("BSD") >= 0 ||
os.indexOf("AIX") >= 0 || os.indexOf("HP-UX") >= 0 ||
os.indexOf("IRIX") >= 0 || os.indexOf("OSF") >= 0 ||
os.indexOf("A/UX") >= 0 || os.indexOf("VMS") >= 0);
}
}