001: package pygmy.handlers;
002:
003: import pygmy.core.*;
004:
005: import java.io.*;
006: import java.security.MessageDigest;
007: import java.security.NoSuchAlgorithmException;
008: import java.net.HttpURLConnection;
009: import java.util.Properties;
010: import java.util.logging.Logger;
011: import java.util.logging.Level;
012:
013: import sun.misc.BASE64Decoder;
014: import sun.misc.BASE64Encoder;
015:
016: /**
017: * <p>
018: * This handler implements the Basic web authentication protocol outlined in RFC 2617. This handler sits in front
019: * of a set of handlers within a chain see {@link pygmy.handlers.DefaultChainHandler}. Everything it's in front of
020: * will be protected. It checks it's url-prefix configuration to know when it should handle a request. Properties used
021: * for configuring:
022: * </p>
023: *
024: * <table class="inner">
025: * <tr class="header"><td>Parameter Name</td><td>Explanation</td><td>Default Value</td><td>Required</td></tr>
026: * <tr class="row"><td>url-prefix</td><td>The prefix to filter request urls.</td><td>None</td><td>Yes</td></tr>
027: * <tr class="altrow"><td>realm</td><td>This is the realm reported to the client. See RFC 2617 for explanation of the realm parameter.</td><td>None</td><td>Yes</td></tr>
028: * <tr class="row"><td>users</td><td>This the path to a file containing all the users and their passwords allowed to access this url.
029: * To create a file you can run this class and hand it the file, username, and password to create. <b>WARNING</b> do
030: * not put this file in a place where it could be downloaded through this server.</td><td>None</td><td>Yes</td></tr>
031: * </table>
032: *
033: * <p>
034: * Here is the syntax for running this class to create a password file:
035: * </p>
036: * <div class="code">
037: * java pgymy.handlers.BasicWebAuthHandler <i><file> <username> <password></i>
038: * </div>
039: * <p>
040: * An existing file can be added to by calling this program again.
041: * </p>
042: */
043: public class BasicWebAuthHandler extends AbstractHandler implements
044: Handler {
045: private static final Logger log = Logger
046: .getLogger(BasicWebAuthHandler.class.getName());
047: private Properties users;
048:
049: public static final ConfigOption REALM_OPTION = new ConfigOption(
050: "realm", "", "The default realm to authenticate against.");
051: public static final ConfigOption USERS_OPTION = new ConfigOption(
052: "users", true, "The file used to authenticate users.");
053:
054: public boolean initialize(String handlerName, Server server) {
055: super .initialize(handlerName, server);
056: this .users = new Properties();
057: return loadProperties();
058: }
059:
060: private boolean loadProperties() {
061: InputStream is = null;
062: try {
063: is = new BufferedInputStream(new FileInputStream(
064: USERS_OPTION.getProperty(server, handlerName)));
065: users.load(is);
066: is.close();
067: return true;
068: } catch (IOException e) {
069: log.log(Level.SEVERE,
070: "loadPropeties failed due to IOException.", e);
071: return false;
072: }
073: }
074:
075: protected boolean handleBody(HttpRequest request,
076: HttpResponse response) throws IOException {
077: String auth = request.getRequestHeader("Authorization");
078: if (auth == null) {
079: return askForAuthorization(request, response);
080: }
081: int index = auth.indexOf(" ");
082: if (index < -1) {
083: return askForAuthorization(request, response);
084: }
085: auth = auth.substring(index + 1);
086: BASE64Decoder decoder = new BASE64Decoder();
087: auth = new String(decoder.decodeBuffer(auth));
088: String[] credentials = auth.split(":");
089: try {
090: if (!users.containsKey(credentials[0])
091: || !isPasswordVerified(credentials)) {
092: log.severe("Access denied for user " + credentials[0]);
093: return askForAuthorization(request, response);
094: }
095: } catch (NoSuchAlgorithmException e) {
096: log
097: .log(
098: Level.SEVERE,
099: "Authorization failed due to NoSuchAlgorithmException.",
100: e);
101: response
102: .sendError(
103: HttpURLConnection.HTTP_INTERNAL_ERROR,
104: Http
105: .getStatusPhrase(HttpURLConnection.HTTP_INTERNAL_ERROR));
106: return true;
107: }
108: return false;
109: }
110:
111: private boolean isPasswordVerified(String[] credentials)
112: throws NoSuchAlgorithmException {
113: return hashPassword(credentials[1]).equals(
114: users.getProperty(credentials[0]));
115: }
116:
117: private boolean askForAuthorization(HttpRequest request,
118: HttpResponse response) {
119: String realm = REALM_OPTION.getProperty(server, handlerName);
120:
121: response.addHeader("WWW-Authenticate", "Basic realm=\"" + realm
122: + "\"");
123: response.sendError(HttpURLConnection.HTTP_UNAUTHORIZED, Http
124: .getStatusPhrase(HttpURLConnection.HTTP_UNAUTHORIZED));
125: return true;
126: }
127:
128: private static String hashPassword(String password)
129: throws NoSuchAlgorithmException {
130: MessageDigest md5 = MessageDigest.getInstance("MD5");
131: byte[] md5password = md5.digest(password.getBytes());
132: BASE64Encoder encoder = new BASE64Encoder();
133: return encoder.encode(md5password);
134: }
135:
136: public static void main(String[] args) throws IOException,
137: NoSuchAlgorithmException {
138: if (args.length < 3) {
139: System.out
140: .println("Usage: BasicWebAuthHandler <file> <user> <password>");
141: return;
142: }
143:
144: File userFile = new File(args[0]);
145: Properties users = new Properties();
146: if (userFile.exists()) {
147: InputStream is = new BufferedInputStream(
148: new FileInputStream(userFile));
149: users.load(is);
150: is.close();
151: }
152:
153: System.out.println("Creating hash for " + args[1]);
154: users.setProperty(args[1], hashPassword(args[2]));
155:
156: System.out.println("Writing password for " + args[1]);
157: OutputStream os = new BufferedOutputStream(
158: new FileOutputStream(userFile));
159: users.store(os, "");
160: os.flush();
161: os.close();
162: System.out.println("done");
163: }
164:
165: public boolean shutdown(Server server) {
166: return false;
167: }
168: }
|