/* * @(#)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: * * ronald@innovation.ch * * 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