Added JsonPage and finished implementation of digest auth page

This commit is contained in:
Ziver Koc 2016-11-04 16:52:10 +01:00
parent 2c7a9a6eff
commit 26c09452f3
10 changed files with 244 additions and 52 deletions

View file

@ -114,7 +114,8 @@ public class IOUtil {
while((line = in.readLine()) != null){
str.append(line).append("\n");
}
str.delete(str.length()-1, str.length()); // remove last new line
if (str.length() > 0)
str.delete(str.length()-1, str.length()); // remove last new line
if (close) reader.close();
return str.toString();

View file

@ -32,12 +32,12 @@ import java.util.Iterator;
public class HttpHeader {
// HTTP info
private boolean request;
private String type;
private String url;
private boolean request = true;
private String type = "GET";
private String url = "/";
private HashMap<String, String> urlAttributes;
private float version;
private int httpCode;
private float version = 1.0f;
private int httpCode = 200;
private InputStream in;
// Parameters

View file

@ -31,7 +31,7 @@ import zutil.parser.URLDecoder;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
@ -44,7 +44,7 @@ public class HttpHeaderParser {
private InputStream in;
private boolean readStatusLine;
private boolean skipStatusLine;
/**
@ -54,7 +54,7 @@ public class HttpHeaderParser {
*/
public HttpHeaderParser(InputStream in){
this.in = in;
this.readStatusLine = true;
this.skipStatusLine = false;
}
/**
@ -72,7 +72,7 @@ public class HttpHeaderParser {
String line;
// First line
if (readStatusLine) {
if (!skipStatusLine) {
if( (line=IOUtil.readLine(in)) != null && !line.isEmpty() )
parseStatusLine(header, line);
else
@ -90,10 +90,10 @@ public class HttpHeaderParser {
}
/**
* @param readStatusLine indicates if the stream contains http status lines. (default: true)
* @param skipStatusLine indicates if the parser should expect a http status lines. (default: true)
*/
public void setReadStatusLine(boolean readStatusLine){
this.readStatusLine = readStatusLine;
public void skipStatusLine(boolean skipStatusLine){
this.skipStatusLine = skipStatusLine;
}
/**
@ -153,9 +153,25 @@ public class HttpHeaderParser {
public static void parseCookieValues(Map<String,String> map, String cookieValue){
parseHeaderValues(map, cookieValue, ";");
}
/**
* Parses a header value string that contains key and value paired data and
* stores them in a HashMap. If a pair only contain a key the the value
* stores them in a HashMap. If a pair only contain a key then the value
* will be set as a empty string.
*
* TODO: method is not quote aware
* @param headerValue the raw header value String that will be parsed.
* @param delimiter the delimiter that separates key and value pairs (e.g. ';' for Cookies or ',' for Cache-Control)
*/
public static HashMap<String, String> parseHeaderValues(String headerValue, String delimiter){
HashMap<String, String> map = new HashMap<>();
parseHeaderValues(map, headerValue, delimiter);
return map;
}
/**
* Parses a header value string that contains key and value paired data and
* stores them in a HashMap. If a pair only contain a key then the value
* will be set as a empty string.
*
* TODO: method is not quote aware

View file

@ -84,7 +84,7 @@ public class HttpPrintStream extends OutputStream{
*/
public HttpPrintStream(OutputStream out, HttpMessageType type) {
this.out = new PrintStream(out);
this.httpVersion = "1.1";
this.httpVersion = "1.0";
this.message_type = type;
this.res_status_code = 200;
this.headers = new HashMap<String, String>();

View file

@ -98,15 +98,18 @@ public class HttpServer extends ThreadedTCPNetworkServer{
private class SessionGarbageCollector extends TimerTask {
public void run(){
Object[] keys = sessions.keySet().toArray();
int count = 0;
for(Object key : keys){
Map<String,Object> session = sessions.get(key);
// Check if session is still valid
if((Long)session.get(SESSION_TTL_KEY) < System.currentTimeMillis()){
sessions.remove(key);
logger.fine("Removing Session: "+key);
++count;
}
}
if (count > 0)
logger.fine("Removed "+count+" old sessions");
}
}
@ -141,7 +144,6 @@ public class HttpServer extends ThreadedTCPNetworkServer{
return null;
}
private static int noOfConnections = 0;
/**
* Internal class that handles all the requests
*
@ -160,8 +162,6 @@ public class HttpServer extends ThreadedTCPNetworkServer{
}
public void run(){
//logger.finest("New Connection: "+socket.getInetAddress()+" (Ongoing connections: "+(++noOfConnections)+")");
try {
//**************************** PARSE REQUEST *********************************
long time = System.currentTimeMillis();
@ -171,7 +171,6 @@ public class HttpServer extends ThreadedTCPNetworkServer{
logger.finer("No header received");
return;
}
String tmp = null;
//******* Read in the post data if available
if (header.getHeader("Content-Length") != null &&
@ -213,7 +212,6 @@ public class HttpServer extends ThreadedTCPNetworkServer{
out.setStatusCode(200);
out.setHeader("Server", SERVER_VERSION);
out.setHeader("Content-Type", "text/html");
//out.setHeader("Connection", "keep-alive");
out.setCookie(SESSION_ID_KEY, "" + session.get(SESSION_ID_KEY));
if (header.getRequestURL() != null && pages.containsKey(header.getRequestURL())) {
@ -252,7 +250,6 @@ public class HttpServer extends ThreadedTCPNetworkServer{
out.close();
in.close();
socket.close();
//logger.finest("Connection Closed: "+socket.getInetAddress()+" (Ongoing connections: "+(--noOfConnections)+")");
} catch( Exception e ) {
logger.log(Level.WARNING, "Could not close connection", e);
}

View file

@ -1,14 +1,18 @@
package zutil.net.http.page;
import zutil.Encrypter;
import sun.net.www.HeaderParser;
import zutil.Hasher;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpHeaderParser;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream;
import java.io.IOException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
/**
* A abstract page that requires HTTP Digest authentication
@ -18,7 +22,9 @@ import java.util.Map;
* @author Ziver
*/
public abstract class HttpDigestAuthPage implements HttpPage{
private static final String DEAFULT_REALM = "Login";
private static final Logger logger = LogUtil.getLogger();
private static final String DEFAULT_REALM = "Login";
private static final String HTTP_AUTH_HEADER = "WWW-Authenticate";
private static final String HTTP_CLIENT_HEADER = "Authorization";
private static final String AUTH_TYPE = "Digest";
@ -29,9 +35,11 @@ public abstract class HttpDigestAuthPage implements HttpPage{
private static final String AUTH_USERNAME = "username";
private static final String AUTH_URI = "uri";
private static final String AUTH_RESPONSE = "response";
private static final String AUTH_DELIMITER = ",";
private String realm = DEAFULT_REALM;
private String realm = DEFAULT_REALM;
private HashMap<String,String> userMap = new HashMap<>();
private SecureRandom secRandom = new SecureRandom();
@ -40,6 +48,12 @@ public abstract class HttpDigestAuthPage implements HttpPage{
this.realm = realm;
}
public void addUser(String username, char[] password) {
userMap.put(username, new String(password)); // TODO: should use char[] for passwords
}
public void removeUser(String username) {
userMap.remove(username);
}
@Override
@ -49,17 +63,51 @@ public abstract class HttpDigestAuthPage implements HttpPage{
Map<String, String> cookie,
Map<String, String> request) throws IOException {
if (headers.getHeader(HTTP_CLIENT_HEADER) == null) {
if (headers.getHeader(HTTP_CLIENT_HEADER) == null || !session.containsKey(AUTH_NONCE)) {
session.put(AUTH_NONCE, generateNonce());
out.setStatusCode(401);
out.setHeader(HTTP_AUTH_HEADER, generateAuthHeader((String) session.get(AUTH_NONCE)));
out.println("401 Unauthorized");
}
else if ( ! headers.getHeader(HTTP_CLIENT_HEADER).startsWith(AUTH_TYPE)){
out.setStatusCode(501);
out.println("501 Not Implemented");
}
else{
authRespond(out, headers, session, cookie, request);
HashMap<String,String> authMap = HttpHeaderParser.parseHeaderValues(
headers.getHeader(HTTP_CLIENT_HEADER).substring(AUTH_TYPE.length()+1), // Skip auth type
AUTH_DELIMITER);
if (authenticate(
authMap.get(AUTH_USERNAME),
headers.getRequestURL(),
(String)session.get(AUTH_NONCE),
authMap.get(AUTH_RESPONSE))) {
// Safe area, user authenticated
logger.fine("User '"+authMap.get(AUTH_USERNAME)+"' has been authenticated for realm '"+realm+"'");
authRespond(out, headers, session, cookie, request);
}
else{
out.setStatusCode(403);
out.println("403 Forbidden");
}
}
}
private boolean authenticate(String username, String uri, String nonce, String clientResponse){
if (!userMap.containsKey(username)) // do user exist?
return false;
String generatedResponse = generateResponseHash(
generateH1(username, userMap.get(username), realm),
generateH2(uri),
nonce);
if (generatedResponse.equals(clientResponse)){
return true;
}
return false;
}
private String generateAuthHeader(String nonce){
StringBuilder str = new StringBuilder();
str.append(AUTH_TYPE).append(' ');
@ -76,7 +124,7 @@ public abstract class HttpDigestAuthPage implements HttpPage{
}
private static String generateH1(String username, String password, String realm) {
String ha1 = null;
String ha1;
// If the algorithm directive's value is "MD5" or unspecified, then HA1 is
// HA1=MD5(username:realm:password)
ha1 = Hasher.MD5(username +":"+ realm +":"+ password);
@ -111,4 +159,5 @@ public abstract class HttpDigestAuthPage implements HttpPage{
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws IOException;
}

View file

@ -0,0 +1,35 @@
package zutil.net.http.page;
import zutil.net.http.HttpHeader;
import zutil.net.http.HttpPage;
import zutil.net.http.HttpPrintStream;
import zutil.parser.DataNode;
import zutil.parser.json.JSONWriter;
import java.io.IOException;
import java.util.Map;
/**
* @author Ziver on 2016-11-04.
*/
public abstract class HttpJsonPage implements HttpPage {
@Override
public void respond(HttpPrintStream out,
HttpHeader headers,
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request) throws IOException {
out.setHeader("Content-Type", "application/json");
JSONWriter writer = new JSONWriter(out);
writer.write(jsonRespond(headers, session, cookie, request));
writer.close();
}
protected abstract DataNode jsonRespond(HttpHeader headers,
Map<String, Object> session,
Map<String, String> cookie,
Map<String, String> request);
}