diff options
Diffstat (limited to 'java/src/com/memberwebs')
8 files changed, 1360 insertions, 0 deletions
diff --git a/java/src/com/memberwebs/httpauth/BaseHttpAuthenticator.java b/java/src/com/memberwebs/httpauth/BaseHttpAuthenticator.java new file mode 100644 index 0000000..8dd15f1 --- /dev/null +++ b/java/src/com/memberwebs/httpauth/BaseHttpAuthenticator.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth; + +import java.io.IOException; +import java.net.SocketException; +import java.util.Enumeration; + +/** + * Base class for HttpAuth authenticators. This class in not generally used + * outside of the HttpAuth code. + * + * @author Nate Nielsen + */ +public abstract class BaseHttpAuthenticator +{ + protected HttpAuthConnectionSource m_source; + + protected BaseHttpAuthenticator(HttpAuthConnectionSource source) + { + m_source = source; + } + + protected abstract void addResponseHeader(Object response, String name, String value); + protected abstract void setResponseResult(Object response, int code) + throws IOException; + protected abstract Enumeration getHeaderValues(Object request, String name); + protected abstract Enumeration getHeaderNames(Object request); + + protected String authenticateRequest(Object request, Object response, + String connid, String method, + String uri, String[] authtypes) + throws HttpAuthException + { + // Prepare and send the request line + String args = connid + " " + method + " " + uri; + HttpAuthConnection conn = null; + HttpAuthConnection.Response rsp = null; + + try + { + // Retry loop + for(;;) + { + conn = m_source.getOpenConnection(); + + try + { + conn.sendLine("AUTH " + args); + sendHeaders(conn, request, authtypes); + + // If above was successful get out of this retry loop + break; + } + catch(SocketException e) + { + // Always close connections that have errors on them + conn.close(); + + // Stupid hack but java doesn't let us do any better just now + if(!e.getMessage().startsWith("Broken pipe")) + throw new HttpAuthException("error communicating with httpauthd", e); + } + + // The following code only gets executed when our sockets + // have been disconnected. We loop up and try again. We're + // pretty safe in doing this because the act of opening a + // HttpAuthConnection sends some data through it, so only + // stale connections should end up here. + m_source.doneWithConnection(conn); + } + + rsp = conn.readResponse(200); + copyHeaders(conn, response, authtypes); + + if(rsp.ccode != 200) + { + setResponseResult(response, rsp.ccode); + return null; + } + else + { + return rsp.details; + } + } + catch(IOException e) + { + try + { + // Always close connections that have errors on them + conn.close(); + } + catch(IOException ex) + { } + + throw new HttpAuthException("error communicating with httpauthd", e); + } + finally + { + if(conn != null) + m_source.doneWithConnection(conn); + } + } + + protected void copyHeaders(HttpAuthConnection conn, Object response, + String[] authtypes) + throws IOException + { + for(;;) + { + String line = conn.readLine(); + if(line != null) + { + line.trim(); + if(line.length() == 0) + line = null; + } + + if(line == null) + break; + + int pos = line.indexOf(":"); + + // Skip invalid lines + if(pos == -1) + continue; + + String name = line.substring(0, pos).trim(); + String value = line.substring(pos + 1).trim(); + + // Drop junk that's not allowed + if(authtypes != null && authtypes.length > 0) + { + if(name.equalsIgnoreCase("WWW-Authenticate")) + { + boolean ok = false; + + for(int i = 0; i < authtypes.length; i++) + { + if(value.startsWith(authtypes[i])) + { + ok = true; + break; + } + } + + if(!ok) + continue; + } + } + + addResponseHeader(response, name, value); + } + } + + /** + * @param req + */ + protected void sendHeaders(HttpAuthConnection conn, Object request, + String[] authtypes) + throws IOException + { + Enumeration e = getHeaderNames(request); + + while(e.hasMoreElements()) + { + String name = (String)e.nextElement(); + if(!name.equalsIgnoreCase("Authorization")) + continue; + + Enumeration e2 = getHeaderValues(request, name); + while(e2.hasMoreElements()) + { + String value = (String)e2.nextElement(); + + if(authtypes == null || authtypes.length == 0) + { + conn.sendLine(name + ": " + value); + } + else + { + for(int i = 0; i < authtypes.length; i++) + { + if(value.startsWith(authtypes[i])) + { + conn.sendLine(name + ": " + value); + break; + } + } + } + } + } + + // Headers always end in a blank line + conn.sendLine(""); + } +} diff --git a/java/src/com/memberwebs/httpauth/HttpAuthConnection.java b/java/src/com/memberwebs/httpauth/HttpAuthConnection.java new file mode 100644 index 0000000..7604d31 --- /dev/null +++ b/java/src/com/memberwebs/httpauth/HttpAuthConnection.java @@ -0,0 +1,347 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +/** + * A connection to the HttpAuth daemon. + * + * @author Nate Nielsen + */ +public class HttpAuthConnection + implements HttpAuthConnectionSource +{ + /** + * The option to set the authenticaton handler. + */ + public static final String OPT_HANDLER = "Handler"; + + /** + * The option to set the digest domains. + */ + public static final String OPT_DOMAIN = "Domain"; + + private Writer m_output; + private BufferedReader m_input; + private Socket m_socket; + private boolean m_checkedout; + + /** + * Connects to the HttpAuth daemon at the given address and port. The + * appropriate handler is set. + * + * @param hostname The address where the HttpAuth daemon is running. + * @param port The TCP port for the HttpAuth daemon. + * @param handler The HttpAuth handler to use. + */ + public void open(String hostname, int port, String handler) + throws IOException, HttpAuthException + { + HashMap options = new HashMap(); + options.put(OPT_HANDLER, handler); + open(hostname, port, options); + } + + /** + * Connects to the HttpAuth daemon at the given address and port. Various + * options (including the handler) can be set. + * + * @param hostname The address where the HttpAuth daemon is running. + * @param port The TCP port for the HttpAuth daemon. + * @param options The HttpAuth options to use. This must include {@link #OPT_HANDLER}. + */ + public void open(String hostname, int port, Map options) + throws IOException, HttpAuthException + { + synchronized(this) + { + close(); + + m_socket = new Socket(); + m_socket.connect(new InetSocketAddress(hostname, port), 60000); + + m_input = new BufferedReader(new InputStreamReader(m_socket.getInputStream())); + m_output = new OutputStreamWriter(m_socket.getOutputStream()); + + // Okay now read the version number + Response ver = readResponse(100); + if(!ver.details.trim().equals("HTTPAUTH/1.0")) + throw new HttpAuthException("httpauthd speaking wrong version of protocol: " + ver.details); + + // Set any options as necessary + Iterator it = options.keySet().iterator(); + while(it.hasNext()) + { + String name = (String)it.next(); + String value = (String)options.get(name); + + // Set the options request + sendLine("SET " + name + " " + value); + + // This throws an exception if we don't get the right response + readResponse(202); + } + } + } + + /** + * Closes the connection to the HttpAuth daemon. + */ + public void close() + throws IOException + { + synchronized(this) + { + if(m_socket != null && m_output != null) + { + try + { + sendLine("QUIT"); + } + catch(IOException e) + { + } + } + + if(m_output != null) + { + m_output.close(); + m_output = null; + } + + if(m_input != null) + { + m_input.close(); + m_input = null; + } + + if(m_socket != null) + { + m_socket.close(); + m_socket = null; + } + } + } + + /** + * Checks whether we're connected to the HttpAuth daemon. + */ + public boolean isConnected() + { + return m_socket != null && m_socket.isConnected() + && m_input != null && m_output != null; + } + + /** + * Sends a command string to the HttpAuth daemon. + * + * @param string The command to send. + */ + public void sendLine(String string) + throws IOException + { + synchronized(this) + { + m_output.write(string + "\n"); + m_output.flush(); + } + } + + /** + * Reads a response line from the HttpAuth daemon. + * + * @return The response line. + */ + public String readLine() + throws IOException + { + synchronized(this) + { + return m_input.readLine(); + } + } + + /* --------------------------------------------------------------------------- + * HttpAuthConnectionSource + * + * This is a simple implementation which allows a single connection + * to be used in leu of a connection pool. + */ + + /** + * Treats this single connection as a pool. Checks out the current + * connection. Use {@link HttpAuthConnectionPool} instead. + * @return The connection. + */ + public HttpAuthConnection getOpenConnection() + throws HttpAuthException + { + synchronized(this) + { + if(isConnected() && !m_checkedout) + return null; + + m_checkedout = true; + return this; + } + } + + /** + * Treats this single connection as a pool. Checks in the current + * connection. Use {@link HttpAuthConnectionPool} instead. + * @param conn The connection. + */ + public void doneWithConnection(HttpAuthConnection conn) + throws HttpAuthException + { + synchronized(this) + { + m_checkedout = false; + } + } + + // ----------------------------------------------------------------------------- + // Helper functions + + protected Response readResponse(int expected) + throws HttpAuthException, IOException + { + Response resp = new Response(); + String line = readLine(); + + if(line == null) + throw new IOException("unexpected end of data from httpauthd"); + + String word = parseWord(line); + line = line.substring(word.length()); + word = word.trim(); + + try + { + int code = Integer.parseInt(word, 10); + if(code < 100 || code > 599) + throw new NumberFormatException(); + + resp.code = code; + } + catch(NumberFormatException e) + { + throw new HttpAuthException("httpauth protocol error. invalid code received: " + word); + } + + if(expected != 0 && resp.code != expected) + throw new HttpAuthException("httpauth protocol error. expected code '" + expected + "' and got: " + resp.code); + + if(resp.code == 200) + { + word = parseWord(line); + line = line.substring(word.length()).trim(); + word = word.trim(); + + try + { + int code = Integer.parseInt(word, 10); + if(code < 100 || code > 599) + throw new NumberFormatException(); + + resp.ccode = code; + } + catch(NumberFormatException e) + { + throw new HttpAuthException("httpauth protocol error. invalid code received: " + word); + } + } + + if(line.length() > 0) + resp.details = line; + + return resp; + } + + protected String parseWord(String line) + { + int start = -1; + int end; + char ch; + + while(true) + { + if(++start >= line.length()) + return ""; + + ch = line.charAt(start); + if(ch != ' ' && ch != '\t') + break; + } + + end = start - 1; + + while(true) + { + if(++end >= line.length()) + break; + + ch = line.charAt(end); + if(ch == ' ' || ch == '\t') + break; + } + + if(end == -1) + return ""; + + return line.substring(0, end); + } + + class Response + { + int code; + int ccode; + String details; + }; +} diff --git a/java/src/com/memberwebs/httpauth/HttpAuthConnectionPool.java b/java/src/com/memberwebs/httpauth/HttpAuthConnectionPool.java new file mode 100644 index 0000000..089402b --- /dev/null +++ b/java/src/com/memberwebs/httpauth/HttpAuthConnectionPool.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Stack; + +/** + * A pool of connections to the HttpAuth daemon. + * + * @author Nate Nielsen + */ +public class HttpAuthConnectionPool + implements HttpAuthConnectionSource +{ + private Stack m_connections; + private short m_checkedout; + private short m_max; + + private String m_hostname; + private int m_port; + private Map m_options; + + /** + * Creates a new pool of connections to the HttpAuth daemon at the given + * address. The connections will be setup to use the given handler. + * + * @param hostname The address at which the HttpAuth daemon is running. + * @param port The TCP port at which the HttpAuth daemon is listening on. + * @param handler The HttpAuth handler to use. + */ + public HttpAuthConnectionPool(String hostname, int port, String handler) + { + m_hostname = hostname; + m_port = port; + m_checkedout = 0; + m_max = 16; + m_connections = new Stack(); + m_options = new HashMap(); + m_options.put(HttpAuthConnection.OPT_HANDLER, handler); + } + + /** + * Creates a new pool of connections to the HttpAuth daemon at the given + * address. The connections will be setup with the various options. + * + * @param hostname The address at which the HttpAuth daemon is running. + * @param port The TCP port at which the HttpAuth daemon is listening on. + * @param options The HttpAuth options to use. This must include {@link #HttpAuthConnection.OPT_HANDLER}. + */ + public HttpAuthConnectionPool(String hostname, int port, Map options) + { + m_hostname = hostname; + m_port = port; + m_checkedout = 0; + m_max = 16; + m_connections = new Stack(); + m_options = options; + } + + /** + * Set the maximum number of connections available in this pool. + * @param max The maximum number of connections. + */ + public void setMax(short max) + { + m_max = max; + } + + /* --------------------------------------------------------------------------- + * HttpAuthConnectionSource + */ + + /** + * Retrieves a connection from the pool. + * @return The connection. + */ + public HttpAuthConnection getOpenConnection() + throws HttpAuthException + { + synchronized(this) + { + HttpAuthConnection conn = null; + + while(!m_connections.empty()) + { + conn = (HttpAuthConnection)m_connections.pop(); + if(conn != null && conn.isConnected()) + { + m_checkedout++; + return conn; + } + } + + if(m_checkedout >= m_max) + throw new HttpAuthException("too many connections to httpauthd: " + m_checkedout); + + try + { + // Otherwise we make a new connection + conn = new HttpAuthConnection(); + conn.open(m_hostname, m_port, m_options); + m_checkedout++; + return conn; + } + catch(IOException e) + { + throw new HttpAuthException("couldn't connect to httpauthd: " + e.getMessage()); + } + } + } + + /** + * Puts a connection back in the pool. + * @param conn The connection. + */ + public void doneWithConnection(HttpAuthConnection conn) + throws HttpAuthException + { + synchronized(this) + { + if(m_checkedout > 0) + m_checkedout--; + + if(conn.isConnected()) + { + // TODO: I don't know of any way to drain the remaining + // junk out of a socket here without blocking. :( + + m_connections.push(conn); + } + } + } +} diff --git a/java/src/com/memberwebs/httpauth/HttpAuthConnectionSource.java b/java/src/com/memberwebs/httpauth/HttpAuthConnectionSource.java new file mode 100644 index 0000000..eab8eb5 --- /dev/null +++ b/java/src/com/memberwebs/httpauth/HttpAuthConnectionSource.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth; + +/** + * An interface for a pool of HttpAuth connections. + * + * @author Nate Nielsen + */ +public interface HttpAuthConnectionSource +{ + /** + * Retrieves a connection from the pool. + * @return The connection. + */ + public HttpAuthConnection getOpenConnection() + throws HttpAuthException; + + /** + * Puts a connection back in the pool. + * @param conn The connection. + */ + public void doneWithConnection(HttpAuthConnection conn) + throws HttpAuthException; +} diff --git a/java/src/com/memberwebs/httpauth/HttpAuthException.java b/java/src/com/memberwebs/httpauth/HttpAuthException.java new file mode 100644 index 0000000..c342302 --- /dev/null +++ b/java/src/com/memberwebs/httpauth/HttpAuthException.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth; + + +/** + * A general utility exception which can wrap another exception or + * hold it's own message + * + * @author Nate Nielsen + */ +public class HttpAuthException + extends Exception +{ + // The internal exception + private Exception m_exception; + + /** + * Creates a new HttpAuthException with a message + * + * @param message The message to include. + */ + public HttpAuthException(String message) + { + this(message, null); + } + + /** + * Creates a new HttpAuthException with an exception + * held internally. + * + * @param e The internal exception. + */ + public HttpAuthException(Exception e) + { + this(null, e); + } + + /** + * Creates a new HttpAuthException with a message and an + * exception exception held internally. + * + * @param message The message to include. + * @param e The internal exception. + */ + public HttpAuthException(String message, Exception e) + { + super(message); + if(e != null) + m_exception = e; + } + + /** + * Returns the error message of this exception if present, or + * the internal exception. + * + * @return The message. + */ + public String getMessage() + { + String message = super.getMessage(); + + if(message == null && m_exception != null) + message = m_exception.getMessage(); + + return message; + } + + /** + * Returns the internal exception, or this if not present. + * + * @return The exception. + */ + public Exception getException() + { + if(m_exception == null) + return this; + else + return m_exception; + } + + /** + * Converts this object to a string. + * + * @return The string value. + */ + public String toString () + { + if(m_exception != null) + return m_exception.toString(); + else + return super.toString(); + } + + public StackTraceElement[] getStackTrace() + { + return getStackTrace(); + } + + // A dumb id to suppress compiler warnings + private static final long serialVersionUID = 9117749574169057520L; + +} diff --git a/java/src/com/memberwebs/httpauth/HttpAuthFilter.java b/java/src/com/memberwebs/httpauth/HttpAuthFilter.java new file mode 100644 index 0000000..1c7367d --- /dev/null +++ b/java/src/com/memberwebs/httpauth/HttpAuthFilter.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth; + +/** + * Implement this interface if you want to limit the type of + * authentication available for each request. This could allow + * more insecure authentication over SSL for example. + * + * @author Nate Nielsen + */ +public interface HttpAuthFilter +{ + /** + * Return the types of authentication allowed. + * @param address The address for the request. + * @param secure Whether the request came over a secure channel or not. + * @return The types of authentication allowed. + */ + String[] getAuthTypes(String address, boolean secure); +} diff --git a/java/src/com/memberwebs/httpauth/jetty/JettyHttpAuthenticator.java b/java/src/com/memberwebs/httpauth/jetty/JettyHttpAuthenticator.java new file mode 100644 index 0000000..bcca142 --- /dev/null +++ b/java/src/com/memberwebs/httpauth/jetty/JettyHttpAuthenticator.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth.jetty; + +import java.io.IOException; +import java.util.Enumeration; + +import org.mortbay.http.HttpRequest; +import org.mortbay.http.HttpResponse; +import org.mortbay.http.UserPrincipal; +import org.mortbay.http.UserRealm; +import org.mortbay.http.SecurityConstraint.Authenticator; + +import com.memberwebs.httpauth.BaseHttpAuthenticator; +import com.memberwebs.httpauth.HttpAuthConnectionSource; +import com.memberwebs.httpauth.HttpAuthException; +import com.memberwebs.httpauth.HttpAuthFilter; + +/** + * An authenticator for the Jetty web server. An instance of this object + * is used by a Jetty HttpContext to perform authentication. + * + * @author Nate Nielsen + */ +public class JettyHttpAuthenticator + extends BaseHttpAuthenticator + implements Authenticator +{ + protected HttpAuthFilter m_filter; + + /** + * Creates a new JettyHttpAuthenticator. + * + * @param source A source of HttpAuth connections + * (such as {@link #HttpAuthConnectionPool}) + * @param filter An optional filter which determines + * which types of authentication may be performed. + */ + public JettyHttpAuthenticator(HttpAuthConnectionSource source, + HttpAuthFilter filter) + { + super(source); + m_filter = filter; + } + + /** + * Called by Jetty's HttpContext to perform authentication. + * @param realm + * @param pathInContext + * @param request + * @param response + * @return Used by Jetty to identify the user. + */ + public UserPrincipal authenticated(UserRealm realm, String pathInContext, + HttpRequest request, HttpResponse response) + throws IOException + { + // We ignore the realm + + String[] authtypes = null; + + if(m_filter != null) + authtypes = m_filter.getAuthTypes(request.getRemoteAddr(), request.isConfidential()); + + String user = null; + + try + { + user = authenticateRequest(request, response, "XXX", request.getMethod(), + request.getURI().toString(), authtypes); + } + catch(HttpAuthException e) + { + throw new IOException(e.getMessage()); + } + + if(user == null) + return null; + + HttpAuthPrincipal principal = new HttpAuthPrincipal(user); + + request.setAuthType("HTTPAUTH"); + request.setAuthUser(user); + request.setUserPrincipal(principal); + + return principal; + } + + /** + * Called by Jetty's HttpContext to get authentication method. + * @return Used by Jetty to identify the method. + */ + public String getAuthMethod() + { + return "HTTPAUTH"; + } + + /* ------------------------------------------------------------------------- + * OVERRIDES + */ + + protected void addResponseHeader(Object response, String name, String value) + { + HttpResponse resp = (HttpResponse)response; + resp.addField(name, value); + } + + protected void setResponseResult(Object response, int code) + throws IOException + { + HttpResponse resp = (HttpResponse)response; + + if(code >= 300) + resp.sendError(code); + else + resp.setStatus(code); + } + + protected Enumeration getHeaderValues(Object request, String name) + { + HttpRequest req = (HttpRequest)request; + return req.getFieldValues(name); + } + + protected Enumeration getHeaderNames(Object request) + { + HttpRequest req = (HttpRequest)request; + return req.getFieldNames(); + } + + protected class HttpAuthPrincipal + implements UserPrincipal + { + private String m_name; + + public HttpAuthPrincipal(String name) + { + m_name = name; + } + + public boolean isAuthenticated() + { + return true; + } + + public boolean isUserInRole(String role) + { + return false; + } + + public String getName() + { + return m_name; + } + } + + // A dumb ID to suppress compiler warnings + private static final long serialVersionUID = -1266258707017895782L; +} diff --git a/java/src/com/memberwebs/httpauth/servlet/ServletHttpAuthenticator.java b/java/src/com/memberwebs/httpauth/servlet/ServletHttpAuthenticator.java new file mode 100644 index 0000000..4e2d025 --- /dev/null +++ b/java/src/com/memberwebs/httpauth/servlet/ServletHttpAuthenticator.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2004, Nate Nielsen + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * CONTRIBUTORS + * Nate Nielsen <nielsen@memberwebs.com> + * + */ + +package com.memberwebs.httpauth.servlet; + +import java.io.IOException; +import java.util.Enumeration; + +import javax.servlet.ServletException; +import javax.servlet.UnavailableException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import com.memberwebs.httpauth.BaseHttpAuthenticator; +import com.memberwebs.httpauth.HttpAuthConnectionSource; +import com.memberwebs.httpauth.HttpAuthException; + +/** + * A class which can be used by servlets to authenticate the user for + * a given request. + * + * Note that the user will not be set on the request and will not be + * available via req.getRemoteUser(). + * + * @author Nate Nielsen + */ + +public class ServletHttpAuthenticator + extends BaseHttpAuthenticator +{ + /** + * Creates a new ServletHttpAuthenticator. + * + * @param source A source of HttpAuth connections + * (such as {@link #HttpAuthConnectionPool}) + */ + public ServletHttpAuthenticator(HttpAuthConnectionSource source) + { + super(source); + } + + /** + * Authenticate a request. If the authentication fails, headers and + * a response will be sent. + * + * @param req The HTTP request. + * @param resp The HTTP response used when authentication fails. + * @return The authenticated user name, or null if unsuccessful. + */ + public String authenticate(HttpServletRequest req, HttpServletResponse resp) + throws ServletException + { + return authenticate(req, resp, null); + } + + /** + * Authenticate a request. If the authentication fails, headers and + * a response will be sent. The caller can limit the types of + * authentication allowed. + * + * @param req The HTTP request. + * @param resp The HTTP response used when authentication fails. + * @param authtypes The types of authentication allowed. + * @return The authenticated user name, or null if unsuccessful. + */ + public String authenticate(HttpServletRequest req, HttpServletResponse resp, + String[] authtypes) + throws ServletException + { + String method = req.getMethod(); + String uri = req.getRequestURI(); + + String qs = req.getQueryString(); + if(qs != null) + uri += qs; + + // NOTE: We can't set the remote user on the request. That's just + // the way the interfaces are designed + + try + { + return authenticateRequest(req, resp, "XXX", method, uri, authtypes); + } + catch(HttpAuthException e) + { + throw new UnavailableException(e.getMessage(), 30); + } + } + + /* ------------------------------------------------------------------------- + * OVERRIDES + */ + + protected void addResponseHeader(Object response, String name, String value) + { + HttpServletResponse resp = (HttpServletResponse)response; + resp.addHeader(name, value); + } + + protected void setResponseResult(Object response, int code) + throws IOException + { + HttpServletResponse resp = (HttpServletResponse)response; + if(code >= 200 && code < 300) + resp.setStatus(code); + else + resp.sendError(code); + } + + protected Enumeration getHeaderValues(Object request, String name) + { + HttpServletRequest req = (HttpServletRequest)request; + return req.getHeaders(name); + } + + protected Enumeration getHeaderNames(Object request) + { + HttpServletRequest req = (HttpServletRequest)request; + return req.getHeaderNames(); + } +} |