All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sunlabs.brazil.handler.BasicAuthHandler Maven / Gradle / Ivy

The newest version!
/*
 * BasicAuthHandler.java
 *
 * Brazil project web application toolkit,
 * export version: 2.3 
 * Copyright (c) 1998-2006 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.3
 * Created by suhler on 98/09/14
 * Last modified by suhler on 06/11/13 15:01:57
 *
 * Version Histories:
 *
 * 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. *
* There are several different types of authentication possible. All * authentication handlers should follow these basic principles:
    *
  • The authentication handler examines some aspect of the request * to decide if the appropriate authentication is present. *
  • If the request is acceptable, the authentication handler should * insert the extracted Session ID into a request property * and then return false, to allow subsequent handlers * to run and perhaps use the Session ID. *
  • If the request is not acceptable, the authentication handler can * return an error message or do some other thing to try to obtain a * valid authentication. *
  • Handlers wishing to be protected by authentication should not * subclass an authentication handler. Instead, such handler should * be written to assume that authentication has already been performed * and then just examine the Session ID present. * The web developer is then responsible for choosing which one (of * possibly many) forms of authentication to use and installing those * authentication handlers before the "sensitive" handler. *
  • Handlers that are protected by an authentication handler can * use the Session ID stored in the request properties regardless of * the specifics of the authentication handler. *
*
 * 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. *


* This handler uses the following configuration properties: *
*
prefix, suffix, glob, match *
Sepcify the URL that triggers this handler. * *
code *
The type of authentication to perform. The default value is 401. *

* 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 *
If specified, this is the request header that will contain the * "basic" authentication string, instead of the "Authorization" * or "Proxy-Authorization" header implied by code. * * *
authenticate *
If specified, this is the response header that will be sent in the * HTTP error response if the user is not authenticated. *

* 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 *
The "realm" of the HTTP authentication error message. This is a * string that the browser is supposed to present to the client when * asking the client the authenticate. It provides a human-friendly * name describing who wants the authentication. * *
message *
The body of the HTTP authentication error message. This will be * displayed by the browser if the client chooses not to authenticate. * The default value is "". Patterns of the form ${xxx} are * replaced with the value of the xxx * entry of request.props. * *
mapFile *
If specified, this is the initial Session ID file. * This is expected to be * a java properties file, whose keys are the authentication tokens, * and whose values are the Session IDs that are inserted into the * request properties. *

* The keys in the file are basic authentication (base64) tokens with * any trailing "=" characters changed to "!". * *

session *
The name of the request property that the Session ID will be stored * in, to be passed to downstream handlers. The default value is * "SessionID". * *
ident *
The 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. *
* * @author Stephen Uhler ([email protected]) * @author Colin Stevens ([email protected]) * @version 2.3, 06/11/13 */ public class BasicAuthHandler implements Handler { private static final String CODE = "code"; private static final String AUTHORIZATION = "authorization"; private static final String AUTHENTICATE = "authenticate"; private static final String REALM = "realm"; private static final String MESSAGE = "message"; private static final String MAP_FILE = "mapFile"; private static final String SESSION = "session"; private static final String IDENT = "ident"; MatchString isMine; // check for matching url public int code = 401; public String authorization = "Authorization"; public String authenticate = "WWW-Authenticate"; public String realm = "realm"; public String message = "Invalid credentials supplied"; public String mapFile = null; public String session = "SessionID"; public String ident = "authorized"; public String sessionTable = null; String propsPrefix; /** * Initializes this handler. It is an error if the mapFile * parameter is specified but that file cannot be loaded. * * @param server * The HTTP server that created this handler. * * @param prefix * 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; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy