/*
* BasicAuthHandler.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 1998-2008 Sun Microsystems, Inc.
*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is included as the file "license.terms",
* and also available at http://www.sun.com/
*
* The Original Code is from:
* Brazil project web application toolkit release 2.3.
* The Initial Developer of the Original Code is: suhler.
* Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): cstevens, suhler.
*
* Version: 2.4
* Created by suhler on 98/09/14
* Last modified by suhler on 08/03/04 11:10:28
*
* Version Histories:
*
* 2.4 08/03/04-11:10:28 (suhler)
* javadoc fixes
*
* 2.3 06/11/13-15:01:57 (suhler)
* move MatchString to package "util" from "handler"
*
* 2.2 03/08/01-16:18:40 (suhler)
* fixes for javadoc
*
* 2.1 02/10/01-16:36:33 (suhler)
* version change
*
* 1.33 02/07/24-10:45:49 (suhler)
* doc updates
*
* 1.32 02/01/17-19:12:33 (suhler)
* - static table is kept in SessionManager, so "Static" and "dynamic" modes
* may interoperate.
* - if the valid credentials are "", then no SessionId property is set
* .
*
* 1.31 01/08/14-16:37:36 (suhler)
* small changes to allow dynamic authorization tables to interoperate
* with the SetTemplate
*
* 1.30 01/07/20-11:30:33 (suhler)
* MatchUrl -> MatchString
*
* 1.29 01/07/17-14:15:45 (suhler)
* use MatchUrl
*
* 1.28 01/03/13-15:12:17 (cstevens)
* Bug loading message resource.
*
* 1.27 01/01/12-08:25:13 (suhler)
* make map table public, so it may be manipulated from server-side scripts
*
* 1.26 00/12/28-12:13:30 (suhler)
* doc fixes
*
* 1.25 00/12/11-13:27:18 (suhler)
* add class=props for automatic property extraction
*
* 1.24 00/10/06-11:07:22 (suhler)
* "subst" moved from PRopsTemplate to Format
*
* 1.23 00/10/05-10:43:39 (suhler)
*
* 1.22 00/08/16-12:11:07 (suhler)
* replaced Format with PRopsTemplate.getPRoperty
*
* 1.21 00/07/05-13:45:24 (cstevens)
* BasicAuthHandler was giving misleading error message indicating that it
* couldn't load the mapfile even if no mapfile was specified.
*
* 1.20 00/06/05-11:03:14 (suhler)
* doc fixes
*
* 1.19 00/05/31-13:47:53 (suhler)
* name change
*
* 1.18 00/05/22-14:04:04 (suhler)
* remove ErrorMsg
*
* 1.17 00/05/19-11:49:39 (suhler)
* doc cleanups, better diagnostice, fixed broken http header processing
*
* 1.16 00/04/28-09:22:12 (suhler)
* always set "gotCookie" property
*
* 1.15 00/04/20-11:48:48 (cstevens)
* copyright.
* rename.
*
* 1.14 00/04/17-14:22:49 (cstevens)
* Doc
*
* 1.13 00/04/12-15:52:25 (cstevens)
* documentation
*
* 1.12 00/03/29-14:34:00 (cstevens)
* AuthHandler handles authentication via 401 or 407 (Proxy-Authentication).
*
* 1.11 00/03/10-17:02:00 (cstevens)
* Removing unused member variables
*
* 1.10 99/11/16-19:07:46 (cstevens)
* wildcard imports.
*
* 1.9 99/10/26-18:50:21 (cstevens)
* case
*
* 1.8 99/10/26-17:03:40 (cstevens)
* Get rid of public variables Request.server and Request.sock
* In all cases, Request.server was not necessary in the Handler.
* Request.sock was changed to Request.getSock(); it is still rarely used for
* diagnostics and loggin (e.g., ChainSawHandler).
*
* 1.7 99/10/01-11:25:42 (cstevens)
* Change logging to show prefix of Handler generating the log message.
*
* 1.6 99/09/15-14:38:24 (cstevens)
* Rewriting http server.
*
* 1.5 99/07/12-09:25:56 (suhler)
* renamed "oops" and made available to package
*
* 1.4 99/04/01-09:03:47 (suhler)
* credentials file can now be resolved relative to the document root
*
* 1.3 99/03/30-09:28:39 (suhler)
* documentation update
*
* 1.2 98/09/21-14:53:28 (suhler)
* changed the package names
*
* 1.2 98/09/14-18:03:03 (Codemgr)
* SunPro Code Manager data about conflicts, renames, etc...
* Name history : 4 3 handlers/BasicAuthHandler.java
* Name history : 3 2 handlers/BasicSessionHandler.java
* Name history : 2 1 handlers/AuthHandler.java
* Name history : 1 0 AuthHandler.java
*
* 1.1 98/09/14-18:03:02 (suhler)
* date and time created 98/09/14 18:03:02 by suhler
*
*/
package sunlabs.brazil.handler;
import sunlabs.brazil.server.Handler;
import sunlabs.brazil.server.Request;
import sunlabs.brazil.server.Server;
import sunlabs.brazil.session.SessionManager;
import sunlabs.brazil.util.Format;
import sunlabs.brazil.util.MatchString;
import java.io.InputStream;
import java.util.Properties;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.io.IOException;
/**
* The BasicAuthHandler
obtains a Session ID by performing
* "basic" authentication, using either the "Authorization" or the
* "Proxy-Authorization" headers. This handler prevents
* subsequent downstream handlers from being accessed unless the proper
* authentication was seen in the request. The Session ID obtained by this
* handler is meant to be used by those downsteams handlers to access
* whatever session-dependent information they need.
*
* If the request does not contain the authentication headers or the
* authentication information is not valid, this handler sends an HTTP
* error message along with the "WWW-Authenticate" or "Proxy-Authenticate"
* header, as appropriate. See
* code
,
* authorization
,
* authenticate
*
* If the request does contain valid authentication information, the
* Session ID associated with the authentication information is inserted
* into the request properties, for use by downstream handlers. After
* inserting the Session ID, this handler returns false
to
* allow the downstream handlers to run.
* IF the Session ID in empty (e.g. ""), then, although authenticateion
* succeeds, no Session Id property is set.
*
* The set of valid Session IDs is contained
* in a globally accessible table managed by the SessionManager
,
* which may be initialized with a static table
* (see mapFile
).
*
* The format of the initialization table (if any) described above is a * Java properties file where keys are the Base64 encoded strings obtained * from the Authentication header and the values are the associated Session * IDs. Base64 strings can contain the '=' character, but the keys in a * Java properties file cannot contain an '=' character, so all '=' characters * in the Base64 strings must be converted to '!' in the properties file, * as shown in the following sample properties file: *
* bXIuIGhhdGU6a2ZqYw!! = radion * Zm9vOmJhcg!! = foo ** The data in the SessionManager table doesn't use the '!'s, only ='s. *
false
, to allow subsequent handlers
* to run and perhaps use the Session ID.
* * handlers=auth history file * * auth.class=BasicAuthHandler * auth.session=account * auth.message=Go away, you're not allowed here! * * history.class=HistoryHandler * history.session=account * * file.class=FileHandler * file.root=htdocs ** In the sample pseudo-configuation file specified above, the *
BasicAuthHandler
is first invoked to see if the HTTP "basic"
* authentication header is present in the request. If it isn't, a nasty
* message is sent back. If the "basic" authentication header is present
* and corresponds to a user that the BasicAuthHandler
knows
* about, the Session ID associated with that user is stored in the specified
* property named "account".
*
* Subsequently, the HistoryHandler
examines its specified
* property (also "account") for the Session ID and uses that to keep
* track of which session is issuing the HTTP request.
*
* Each handler that needs a Session ID should have a * configuration parameter that allows the web developer to specify the * name of the request property that holds the Session ID. * Multiple handlers can all use the same request property as each other, * all protected by the same authentication handler. *
code
* * The value 401 corresponds to standard "basic" authentication. * The "Authorization" request header is supposed to contain the * authentication string. If the request was not authenticated, the * "WWW-Authenticate" header is sent in the HTTP error response * to cause the browser to prompt the client to authenticate. *
* The value 407 corresponds to "basic" proxy/firewall authentication. * The "Proxy-Authorization" request header is supposed to contain the * authentication string. If the request was not authenticated, the * "Proxy-Authenticate" header is sent in the HTTP error response * to cause the browser to prompt the client to authenticate. *
* Any other value may also be specified. Whatever the value, it will * be returned as the HTTP result code of the error message. * * *
authorization
* code
.
*
*
* authenticate
* * If this string is "", then this handler will * authenticate the request if the authorization header is present, * but will not send an HTTP error message if the request could * not be authenticated. This is useful if the web developer wants to * do something more complex (such as invoking an arbitrary set of * handlers) instead of just sending a simple error message if the * request was not authenticated. In this case, the web developer can * determine that the request was not authenticated because no * Session ID will be present in the request properties. * *
realm
* message
* request.props
.
*
* mapFile
*
* The keys in the file are basic authentication (base64) tokens with
* any trailing "="
characters changed to "!"
.
*
*
session
* ident
* ident
argument to {@link SessionManager#getSession}
* to get the table of valid sessions. The default value is
* "authorized". If ident
is of the form
* ident:session
, then the session
* portion is used as the session
argument to
* SessionManager.get(). Otherwise the session
* argument is NULL. This table may be manipulated with the SetTemplate,
* using the "ident" namespace and "session" for the
* SetTemplate "sessionTable" parameter.
* mapFile
* parameter is specified but that file cannot be loaded.
*
* @param server
* The HTTP server that created this handler.
*
* @param propsPrefix
* A prefix to prepend to all of the keys that this
* handler uses to extract configuration information.
*
* @return true
if this Handler
initialized
* successfully, false
otherwise.
*/
public boolean
init(Server server, String propsPrefix)
{
Properties props = server.props;
this.propsPrefix = propsPrefix;
isMine = new MatchString(propsPrefix, server.props);
try {
String str = props.getProperty(propsPrefix + CODE);
code = Integer.decode(str).intValue();
} catch (Exception e) {}
if (code == 407) {
authorization = "Proxy-Authorization";
authenticate = "Proxy-Authenticate";
}
authorization = props.getProperty(propsPrefix + AUTHORIZATION, authorization);
authenticate = props.getProperty(propsPrefix + AUTHENTICATE, authenticate);
realm = props.getProperty(propsPrefix + REALM, realm);
message = props.getProperty(propsPrefix + MESSAGE, message);
mapFile = props.getProperty(propsPrefix + MAP_FILE, mapFile);
session = props.getProperty(propsPrefix + SESSION, session);
ident = props.getProperty(propsPrefix + IDENT, ident);
int i = ident.indexOf(":");
if (i>0) {
sessionTable=ident.substring(i+1);
ident = ident.substring(0,i);
}
try {
if (message.startsWith("@")) {
message = ResourceHandler.getResourceString(server.props,
propsPrefix, message.substring(1));
}
} catch (IOException e) {
server.log(Server.LOG_ERROR, propsPrefix,
"Can't get \"denied\" message");
}
if (mapFile != null) try {
server.log(Server.LOG_DIAGNOSTIC, propsPrefix,
"Loading credentials file " + mapFile);
InputStream in = ResourceHandler.getResourceStream(server.props,
propsPrefix, mapFile);
Properties p = new Properties();
p.load(in);
in.close();
Properties map = (Properties) SessionManager.getSession(ident,
sessionTable, Properties.class);
Enumeration keys = p.keys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = (String) p.get(key);
map.put(key.replace('!', '='), value);
}
} catch (Exception e) {
server.log(Server.LOG_DIAGNOSTIC, propsPrefix,
"Credentials file (" + mapFile + ") not available: " + e);
}
mapFile = null;
return true;
}
/**
* Looks up the credentials for this request, and insert them into the
* request stream. If no credentials are found, prompt the user for them.
*/
public boolean
respond(Request request) throws IOException {
if (!isMine.match(request.url)) {
return false;
}
String auth = request.headers.get(authorization);
if (auth == null) {
return complain(request,"Missing http header: " + authorization);
}
try {
/*
* Strip off "basic" at beginning of value.
*/
StringTokenizer st = new StringTokenizer(auth);
if ("basic".equalsIgnoreCase(st.nextToken()) == false) {
return complain(request, "Non-basic realm: " + auth);
}
auth = st.nextToken();
String id;
request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
"Session manager: " + ident + ":" + sessionTable);
Properties h = (Properties) SessionManager.getSession(ident,
sessionTable, Properties.class);
id = (String) h.getProperty(auth);
if (id == null) {
return complain(request, "no id matching: " + auth);
}
/*
* Found registered user, so add user to request properties.
*/
if (!id.equals("")) {
request.props.put(session, id);
request.props.put("gotCookie", "true");
request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
"Setting " + session + " to: " + id);
} else {
request.log(Server.LOG_DIAGNOSTIC, propsPrefix,
"Authorization accepted, no session provided");
}
return false;
} catch (Exception e) {
/* Malformed authorization. */
return complain(request, e.toString());
}
}
/**
* Authentication failed.
* Send the appropriate authentication required header as a response.
* @param request The request to respond to
* @param reason The reason for failure (for diagnostics)
* @return True
*/
public boolean
complain(Request request, String reason)
throws IOException
{
if (authenticate.length() == 0) {
request.log(Server.LOG_DIAGNOSTIC, propsPrefix, "no authenticate?");
return false;
}
request.addHeader(authenticate, "basic realm=\"" + realm + "\"");
request.sendResponse(Format.subst(request.props, message),
"text/html", code);
request.log(Server.LOG_DIAGNOSTIC, propsPrefix, reason);
return true;
}
}