package net.jumperz.app.MGuardian.plugin; import java.io.*; import java.util.regex.*; import java.util.*; import net.jumperz.net.*; import net.jumperz.net.exception.MHttpIOException; import net.jumperz.util.*; import net.jumperz.security.MBlowfishCipher; import net.jumperz.security.MTokenGenerator; public class MBasicAuthenticationLogout extends MGuardianPlugin { private final static String REALM_ID_PARAM_NAME = "guardian_ba_realm_id"; private final static String LOGOUT_PARAM_NAME = "guardian_ba_logout"; private final static String REALM_PARAM_NAME = "guardian_ba_realm"; private final static String REDIRECTED_PARAM_NAME = "guardian_ba_redirected"; private final static String LAST_VISIT_PARAM_NAME = "guardian_ba_last_visit"; private final static String DEFAULT_TIMEOUT = "30"; // minute; private final static String DEFAULT_KEY = "613925036696971b7304ff84ead72437"; // 16 characters = 128 bit private final static String DEFAULT_FORCECOOKIE = "true"; private MTokenGenerator tokenGenerator; private long timeout; private String key; private MBlowfishCipher cipher; private boolean forceCookie; // -------------------------------------------------------------------------------- public void startup() throws IOException { timeout = Integer.parseInt( control.getProperty( "basicAuthenticationLogout.timeout", DEFAULT_TIMEOUT ) ); key = control.getProperty( "basicAuthenticationLogout.key", DEFAULT_KEY ); String forceCookieStr = control.getProperty( "basicAuthenticationLogout.forceCookie", DEFAULT_FORCECOOKIE ); forceCookie = MStringUtil.meansTrue( forceCookieStr ); try { tokenGenerator = new MTokenGenerator(); cipher = new MBlowfishCipher( key ); } catch( Exception e ) { e.printStackTrace(); throw new IOException( e.getMessage() ); } } //-------------------------------------------------------------------------------- public Map execute( Map sessionInfo ) throws IOException { String arg = ( String )sessionInfo.get( "arg" ); try { if( arg.indexOf( "request" ) == 0 ) { return processRequest( sessionInfo, arg ); } else if( arg.equals( "response" ) ) { return processResponse( sessionInfo ); } else if( arg.indexOf( "logout" ) == 0 ) { return processLogout( sessionInfo, arg ); } } catch( IOException e ) { Map pluginResult = new HashMap(); pluginResult.put( "block", new Boolean( true ) ); pluginResult.put( "log", new Boolean( true ) ); pluginResult.put( "message", e.getMessage() ); return pluginResult; } catch( Exception e ) { e.printStackTrace(); return null; } return null; } // -------------------------------------------------------------------------------- private Map processLogout( Map sessionInfo, String arg ) throws IOException { String[] args = arg.split( "\\s" ); String bodyStr = "You have been successfully logout."; if( args.length >= 2 ) { String fileName = arg.substring( arg.indexOf( args[ 1 ] ) ); bodyStr = MStringUtil.loadStrFromFile( fileName ); } MHttpResponse response = new MHttpResponse(); response.setStatusLine( "HTTP/1.0 200 OK" ); response.setBody( bodyStr ); response.addHeaderValue( "Set-Cookie", LOGOUT_PARAM_NAME + "=1;Path=/" ); response.addHeaderValue( "Set-Cookie", REALM_ID_PARAM_NAME + "=0;Path=/" ); response.setHeaderValue( "Content-Length", response.getBodyAsString().length() + "" ); response.setHeaderValue( "Pragma", "no-cache" ); response.setHeaderValue( "Cache-Control", "no-cache" ); Map pluginResult = new HashMap(); pluginResult.put( "response", response ); pluginResult.put( "pass", new Boolean( true ) ); return pluginResult; } // -------------------------------------------------------------------------------- private Map processResponse( Map sessionInfo ) throws IOException { MHttpResponse response = ( MHttpResponse )sessionInfo.get( "response" ); MHttpRequest request = ( MHttpRequest )sessionInfo.get( "request" ); if( response == null ) { throw new IOException( "Response is null." ); } String realmId = getRealmIdFromRequest( request ); String realmCookie = getCookieValue( request, REALM_ID_PARAM_NAME ); String uriStr = request.getUri(); if( response.getStatusCode() == 401 ) { setLastVisit( response ); String wwwauth = response.getHeaderValue( "WWW-Authenticate" ); String realm = ""; if( wwwauth != null ) { realm = MRegEx.getMatch( "realm=\"(.*)\"", wwwauth ); } if( uriStr.indexOf( REALM_ID_PARAM_NAME ) > -1 && ( realmCookie.equals( realmId + "401" ) ) ) { response.setHeaderValue( "WWW-Authenticate", "Basic realm=\"" + realm + " " + realmId + "\"" ); } else if( uriStr.indexOf( REALM_ID_PARAM_NAME ) > -1 && realmCookie.equals( "" ) ) { if( forceCookie ) { response = new MHttpResponse(); response.setStatusLine( "HTTP/1.0 403 Forbidden" ); String bodyStr = "Please enable Cookies"; response.setBody( bodyStr ); response.setHeaderValue( "Content-Type", "text/plain" ); response.setHeaderValue( "Content-Length", bodyStr ); } } else { realmId = tokenGenerator.getToken(); String host = request.getHeaderValue( "Host" ); String protocol = ( String )sessionInfo.get( "protocol" ); if( protocol.equals( "HTTPS" ) ) { protocol = "https"; } else { protocol = "http"; } uriStr = uriStr.replaceFirst( "[&]?" + REALM_ID_PARAM_NAME + "=[a-fA-F0-9]*", "" ); MRequestUri ru = new MRequestUri( uriStr ); String query = ru.getQuery(); if( query.equals( "" ) ) { query = REALM_ID_PARAM_NAME + "=" + realmId; } else { query += "&" + REALM_ID_PARAM_NAME + "=" + realmId; } ru.setQuery( query ); String location = protocol + "://" + host + ru.toString(); response.setStatusLine( "HTTP/1.0 307 Temporary Redirect" ); response.setHeaderValue( "Location", location ); response.removeHeaderValue( "WWW-Authenticate" ); response.addHeaderValue( "Set-Cookie", REALM_ID_PARAM_NAME + "=" + realmId + ";Path=/" ); response.addHeaderValue( "Set-Cookie", LOGOUT_PARAM_NAME + "=1;Path=/" ); response.addHeaderValue( "Set-Cookie", REALM_PARAM_NAME + "=" + realm + ";Path=/" ); } } else { if( uriStr.indexOf( REALM_ID_PARAM_NAME ) > 0 ) { //successfully login response.addHeaderValue( "Set-Cookie", LOGOUT_PARAM_NAME + "=0;Path=/" ); response.addHeaderValue( "Set-Cookie", REALM_ID_PARAM_NAME + "=0;Path=/" ); } if( request.headerExists( "Authorization" ) ) { setLastVisit( response ); } } return null; } // -------------------------------------------------------------------------------- private void setLastVisit( MHttpResponse response ) throws IOException { byte[] encByte = cipher.encrypt( MStringUtil.getBytes( Long.toHexString( System.currentTimeMillis() ) ) ); String lastVisit = MStringUtil.byteToHexString( encByte ); response.addHeaderValue( "Set-Cookie", LAST_VISIT_PARAM_NAME + "=" + lastVisit + ";Path=/" ); } // -------------------------------------------------------------------------------- private String getRealmIdFromRequest( MHttpRequest request ) { String realmParam = request.getParameterValue( REALM_ID_PARAM_NAME ); if( Pattern.matches( "^[a-fA-F0-9]+$", realmParam ) ) { return realmParam; } else { return tokenGenerator.getToken(); } } // -------------------------------------------------------------------------------- private Map processRequest( Map sessionInfo, String arg ) throws IOException { String[] args = arg.split( "\\s" ); String needAuthFileName = ""; String bodyStr = "Authorization Required."; if( args.length < 2 ) { } else { bodyStr = MStringUtil.loadStrFromFile( args[ 1 ] ); } MHttpRequest request = ( MHttpRequest )sessionInfo.get( "request" ); String uriStr = request.getUri(); String logoutCookie = getCookieValue( request, LOGOUT_PARAM_NAME ); String realmId = getRealmIdFromRequest( request ); String realmCookie = getCookieValue( request, REALM_ID_PARAM_NAME ); String realm = getCookieValue( request, REALM_PARAM_NAME ); if( uriStr.indexOf( REDIRECTED_PARAM_NAME ) > -1 ) { MHttpResponse response = new MHttpResponse(); response.setStatusLine( "HTTP/1.0 200 OK" ); response.setHeaderValue( "Content-Length", bodyStr.length() + "" ); response.addHeaderValue( "Set-Cookie", REALM_ID_PARAM_NAME + "=0;Path=/" ); response.setBody( bodyStr ); Map pluginResult = new HashMap(); pluginResult.put( "response", response ); pluginResult.put( "pass", new Boolean( true ) ); return pluginResult; } //check timeout if( request.headerExists( "Authorization" ) ) { String lastVisitStr = getCookieValue( request, LAST_VISIT_PARAM_NAME ); if( lastVisitStr.length() > 0 ) { byte[] encByte = MStringUtil.hexStringToByteArray( lastVisitStr ); byte[] decByte = cipher.decrypt( encByte ); lastVisitStr = MStringUtil.byteArrayToString( decByte ); long lastVisit = 0; try { lastVisit = Long.parseLong( lastVisitStr, 16 ); } catch( NumberFormatException ignored ) { ignored.printStackTrace(); } long now = System.currentTimeMillis(); // if( ( now - lastVisit ) > ( 10 * 1000 ) ) //10 seconds long passed = now - lastVisit; long l = ( long )( timeout * 60 * 1000 ); if( passed > l ) { //timeout request.removeHeaderValue( "Authorization" ); } } else { //disabling cookies if( forceCookie ) { request.removeHeaderValue( "Authorization" ); } } } if( uriStr.indexOf( REALM_ID_PARAM_NAME ) == -1 ) { if( logoutCookie.equals( "1" ) ) { //if logout=1, remove the authorization header request.removeHeaderValue( "Authorization" ); } return null; } else { if( logoutCookie.equals( "0" ) ) { return null; } String redirectUri = uriStr.replaceFirst( REALM_ID_PARAM_NAME + "=[a-fA-F0-9]*", REDIRECTED_PARAM_NAME + "=1" ); if( realmCookie.equals( "" ) ) { //disabling cookies if( forceCookie ) { MHttpResponse response = new MHttpResponse(); response.setStatusLine( "HTTP/1.0 403 Forbidden" ); bodyStr = "Please enable Cookies"; response.setBody( bodyStr ); response.setHeaderValue( "Content-Type", "text/plain" ); response.setHeaderValue( "Content-Length", bodyStr.length() + "" ); Map pluginResult = new HashMap(); pluginResult.put( "response", response ); pluginResult.put( "pass", new Boolean( true ) ); return pluginResult; } else { return null; } } else if( realmId.equals( realmCookie ) ) { if( logoutCookie.equals( "1" ) ) { return return401( realmId, realm, redirectUri ); } else { return null; } } else if( ( realmId + "401" ).equals( realmCookie ) ) { return null; } else { return return401( realmId, realm, redirectUri ); } } } // -------------------------------------------------------------------------------- private String getCookieValue( MHttpRequest request, String name ) throws IOException { List cookieList = request.getCookieValueList( name ); if( cookieList.size() > 1 ) { throw new MHttpIOException( "Cookie_Monster_Found." ); } String cookie = request.getCookieValue( name ); if( cookie.indexOf( '"' ) > -1 ) { throw new MHttpIOException( "Invalid_Cookie_Value." ); } return cookie; } // -------------------------------------------------------------------------------- private Map return401( String realmId, String realm, String redirectUri ) throws IOException { MHttpResponse response = new MHttpResponse(); response.setStatusLine( "HTTP/1.0 401 Authorization Required" ); response.setHeaderValue( "WWW-Authenticate", "Basic realm=\"" + realm + " " + realmId + "\"" ); response.addHeaderValue( "Set-Cookie", REALM_ID_PARAM_NAME + "=" + realmId + "401;Path=/" ); response.addHeaderValue( "Set-Cookie", LOGOUT_PARAM_NAME + "=1;Path=/" ); String bodyStr = ""; response.setHeaderValue( "Content-Length", bodyStr.length() + "" ); response.setBody( bodyStr ); Map pluginResult = new HashMap(); pluginResult.put( "response", response ); pluginResult.put( "pass", new Boolean( true ) ); return pluginResult; } //-------------------------------------------------------------------------------- }