@DBTable(table="user")
public class JiffyUser extends JiffyModel 
{
	@DBColumn
	public String userName;
	@DBColumn
	public String password;
	@DBColumn
	public String role;
	@DBColumn
	public String email;
	@DBColumn
	public int failedAttempts;
	@DBColumn
	public boolean forcePwChange;
	@DBColumn
	public boolean isFrozen = false;
	@DBColumn
	public Date lastLogonTs;
	

	public static JiffyUser getByUsername(String username) throws Exception
	{
		return DB.selectOne(JiffyUser.class, "WHERE @userName@=?", username);
	}
	
	public static JiffyUser getById(long userId) throws Exception
	{
		return DB.selectOne(JiffyUser.class, "WHERE id=?", userId);
	}
	
	public static void markUserAsLoggedIn(String username) throws Exception
	{
		DB.update(JiffyUser.class, "UPDATE @table@ SET @lastLogonTs@=NOW(), @failedAttempts@=0 WHERE @userName@=?", username);
	}
	public static void incrementFailedAttempts(String username, int failedAttempts) throws Exception
	{
        String updateSql = "UPDATE @table@ SET @failedAttempts@=? WHERE @userName@=?";
        DB.update(JiffyUser.class, updateSql, failedAttempts, username);
	}
	
	public static void freezeUser(String username) throws Exception
	{
		String failSql = "UPDATE @table@ SET @isFrozen@=true, @failedAttempts@=0 WHERE @userName@=?";
        DB.update(JiffyUser.class, failSql, username);
	}
	
	public static void setNewPassword(long userId, String password) throws Exception
	{
		String sql = "UPDATE @table@ SET @password@=?, @forcePwChange@=? WHERE @id@=?";
		DB.update(JiffyUser.class, sql, PasswordUtil.encrypt(password), true, userId);
	}
} @DBTable
public class UserSession implements Serializable
{	
	@DBColumn
	public String sessionId;
	@DBColumn
	public long userId;
	@DBColumn
	public String userName;
	@DBColumn
	public String role;
	@DBColumn
	public String ipAddress;
	@DBColumn
	public java.util.Date logonTime;
	@DBColumn
	public java.util.Date lastUserActivity;
	
	public static UserSession createUserSession(JiffyUser u, String ip) throws Exception
	{     	
		UserSession uSess = new UserSession();
       	uSess.sessionId = "";
       	uSess.userId = u.id;
       	uSess.role = u.role;
       	uSess.userName = u.userName;
       	uSess.ipAddress = ip;
       	uSess.logonTime = new java.util.Date();
       	uSess.lastUserActivity = new java.util.Date();

        JiffyUser.markUserAsLoggedIn(u.userName);
        return uSess;
	}
	
	public static UserSessionList getAll() throws Exception 
	{
	  	return DB.selectAll(UserSessionList.class);
	}   
	
	public static UserSession get(String sessionId) throws Exception 
	{
	  	return DB.selectOne(UserSession.class, "WHERE @sessionId@=?", sessionId);
	}

	public static void deleteAll() throws Exception 
	{
	   	DB.update(UserSession.class, "DELETE FROM @table@");
	}    
	    
	public static void delete(long userId) throws Exception 
	{
	   	DB.update(UserSession.class, "DELETE FROM @table@ WHERE @userId@=?", userId);
	}    
	
	public static long getLoggedInUserCount() throws Exception
	{
	  	return DB.countDistinct(UserSession.class, "@userId@", "WHERE @role@!='admin'");
	}	   
	    
	public static void updateSessionActivity(UserSession session, String requestType) throws Exception 
	{
	    if (session == null)
	        return;
	      
	    DB.update(UserSession.class, "UPDATE @table@ SET @lastUserActivity@=NOW() WHERE @sessionId@=?", session.sessionId);
	}
	
	public static void removeInactiveSessions() throws Exception 
	{
	 	long currTime = System.currentTimeMillis();
	  	long sessionUserActivityTimeout = Jiffy.getInt("sessionUserActivityTimeout");
	    java.util.Date date = new java.util.Date(currTime - sessionUserActivityTimeout);
	               
	    UserSessionList sessions = DB.selectAll(UserSessionList.class, "WHERE @lastUserActivity@ < ?", date);
	        
	    String sql = "DELETE FROM @table@ WHERE @lastUserActivity@ < ?";
	    DB.update(UserSession.class, sql, date);
	        
	    for (int i=0; i<sessions.size(); i++)
	    {
	      	Security.logActivity(sessions.get(i), "Forced Logout");
	    }
	}
	    
	public static List<String> getActiveUsers() throws Exception
	{
	    java.util.Date date = new java.util.Date(System.currentTimeMillis() - (Jiffy.getInt("heartbeatEvictionTime") * 1000));
	    String sql = "SELECT user_name FROM user_session WHERE last_user_activity > ?";
	    DBResult rs2 = DB.select(sql, date);
	    return rs2.getAllRows("user_name");
	}
} public class Flash extends HashMap<String, Object>
{
	private static Logger logger = LogManager.getLogger();
	
	private static final String FLASH = "FLASH";
		
	public static void set(HttpServletRequest req, String key, Object value)
	{
		try
		{
			Flash flash = (Flash)Sessions.get(req, FLASH);
			
			if (flash == null)
			{
				flash = new Flash();
			}
			
			flash.put(key, value);
			
			Sessions.set(req, FLASH, flash);
		}
		catch (Exception ex)
		{
			LogUtil.printErrorDetails(logger, ex);
		}
	}
	
	public static Flash retrieve(HttpServletRequest req) throws Exception
	{
		Flash flash = (Flash)Sessions.get(req, FLASH);
		Sessions.remove(req, FLASH);
		if (flash == null)
		{
			flash = new Flash();
		}
		return flash;
	}
	
	public static void delete(HttpServletRequest req) throws Exception
	{
		Sessions.remove(req, FLASH);
	}
	
} public class Sessions
{
	public static final String JVM = "jvm";
	public static final String DATABASE = "db";
	
	private static final String SESSION = "session.";

	// @X-JVM-safe this member is read-only after init()
	// @Thread-safe this member is read-only after init()
	private static String sessionType = JVM;

	// @X-JVM-Synchronized only called by HtmlServlet.init()
	// @Synchronized only called by HtmlServlet.init()
	public static void init() throws Exception
	{
		if (Jiffy.getBool("isSingleServerDeploy"))
		{
			sessionType = JVM;
		}
		else
		{
			sessionType = DATABASE;
		}
	}

	public static void addSession(HttpServletRequest req, HttpServletResponse res, UserSession appSession) throws Exception
	{
		if (StringUtils.equals(sessionType, DATABASE))
		{
			// create a random session ID, independent of the JVM's session ID
			appSession.sessionId = PasswordUtil.autogeneratePassword(40, PasswordUtil.ALPHA_NUMERIC);

			// write that sessionID to the app ID in the cookie
			Cookies.addCookieExpiresWithBrowser(res, Jiffy.getValue("sessionAppID"), appSession.sessionId);
			
			// create a Map in the Cache to store session variables
			Map<String, Object> map = new HashMap<String, Object>();
			Cache.set(SESSION + appSession.sessionId, map);
		}
		else
		{
			// Just use the normal JVM session here
			req.getSession().setAttribute(Constants.SESSION, appSession);
			appSession.sessionId = req.getSession().getId();
		}

		// Store it in the database
		String sql = "INSERT INTO @table@ (@sessionId@, @userId@, @userName@, @role@, @ipAddress@, @logonTime@, @lastUserActivity@) VALUES (?,?,?,?,?,?,?)";

		DB.update(UserSession.class, sql, appSession.sessionId, appSession.userId, appSession.userName, appSession.role, appSession.ipAddress, appSession.logonTime, appSession.lastUserActivity);

	}

	public static void removeSession(HttpServletRequest req, HttpServletResponse res, UserSession appSession) throws Exception
	{
		if (StringUtils.equals(sessionType, DATABASE))
		{
			// Remove the cookie
			Cookies.removeCookie(res, Jiffy.getValue("sessionAppID"));
			
			// Remove the Map from the Cache with this session ID
			if (appSession != null)
			{
				Cache.delete(SESSION + appSession.sessionId);
			}
		}
		else
		{
			// Just use the normal JVM session here
			req.getSession().removeAttribute(Constants.SESSION);
		}

		// always invalidate the session so a new ID is generated
		req.getSession().invalidate();

		// Delete the session from the DB
		String sql = "DELETE FROM @table@ WHERE @sessionId@=?";
		if (appSession != null)
		{
			DB.update(UserSession.class, sql, appSession.sessionId);
		}
	}

	public static UserSession getSession(HttpServletRequest req)
	{
		if (StringUtils.equals(sessionType, DATABASE))
		{
			String sessionID = Cookies.getCookieValue(req, Jiffy.getValue("sessionAppID"));

			if (StringUtils.isEmpty(sessionID))
			{
				return null;
			}

			try
			{
				UserSession session = DB.selectOne(UserSession.class, "WHERE @sessionId@=?", sessionID);
				return session;
			}
			catch (Exception ex)
			{
				return null;
			}
		}
		else
		{
			return (UserSession) req.getSession().getAttribute(Constants.SESSION);
		}
	}

	public static Object get(HttpServletRequest req, String key) throws Exception
	{
		if (StringUtils.equals(sessionType, DATABASE))
		{
			Map<String, Object> map =  (Map)Cache.get(SESSION + Cookies.getCookieValue(req, Jiffy.getValue("sessionAppID")));

			if (map == null)
			{
				return null;
			}
			
			return map.get(key);
		}
		else
		{
			return req.getSession().getAttribute(key);
		}
	}

	public static void set(HttpServletRequest req, String key, Object value) throws Exception
	{
		if (StringUtils.equals(sessionType, DATABASE))
		{
			String sessionID = SESSION + Cookies.getCookieValue(req, Jiffy.getValue("sessionAppID"));
			Map<String, Object> map =  (Map)Cache.get(sessionID);
			
			if (map == null)
			{
				return;
			}
			
			map.put(key, value);
			Cache.set(sessionID, map);
		}
		else
		{
			req.getSession().setAttribute(key, value);
		}
	}

	public static void remove(HttpServletRequest req, String key) throws Exception
	{
		if (StringUtils.equals(sessionType, DATABASE))
		{
			String sessionID = SESSION + Cookies.getCookieValue(req, Jiffy.getValue("sessionAppID"));
			Map<String, Object> map =  (Map)Cache.get(sessionID);
			
			if (map == null)
			{
				return;
			}
			
			map.remove(key);
			Cache.set(sessionID, map);
		}
		else
		{
			req.getSession().removeAttribute(key);
		}
	}
} public class Roles 
{
	//////////
	// Outline all the roles available in the software
	//////////
	public static final String ADMIN = "admin";
	public static final String USER = "user";

	public static final String[] ALL_SYSTEM_ROLES = new String[]{ADMIN, USER};
	
	// a shortcut to ALL_ROLES, since we can't use [] with Services
	public static final String ALL_ROLES = "any_role";
	// no access for anyone
	public static final String NO_ACCESS = "no_one";
	// everyone is allowed, even those that aren't logged in (e.g. index.jsp)
	public static final String ALL = "all";	
} public class ServiceRequest
{
	public HttpServletRequest request;
	public HttpServletResponse response;
	public UserSession appSession;
	public String requestType;
	public String restID;
	public boolean shouldReturnJson;
	
	public String getParameter(String param)
	{
		return request.getParameter(param);
	}
	
	public int getIntParameter(String param)
	{
		return Integer.parseInt(request.getParameter(param));
	}
	
	public double getDoubleParameter(String param)
	{
		return Double.parseDouble(request.getParameter(param));
	}
	
	public boolean getBooleanParameter(String param)
	{
		return StringUtils.equals(request.getParameter(param), "checked");
	}
	
} public abstract class ServiceResponse 
{
	public abstract void respond(HttpServletRequest req, HttpServletResponse resp) throws Exception;
} public class HtmlResponse extends ServiceResponse
{
	public String nextPage;
	
	@Override
	public void respond(HttpServletRequest req, HttpServletResponse resp) throws Exception
	{
		// Set all responses to redirects for proper routing in the browser
		
		// As a result, we must map the attributes to the Flash object
		
		Enumeration<String> e = req.getAttributeNames();
		while (e.hasMoreElements())
		{
			String key = e.nextElement();
			Object value = req.getAttribute(key);
			Flash.set(req, key, value);
		}
		
		resp.sendRedirect(nextPage);
	}

} public class Constants
{
    // session key
	public static final String SESSION = "jiffy.session";

	// messages used on the UI screens
	public static final String SUCCESS_MESSAGE = "success.message";
	public static final String WARN_MESSAGE = "warn.message";
	public static final String ERROR_MESSAGE = "error.message";
	public static final String INFO_MESSAGE = "info.message";
	public static final String CUSTOM1_MESSAGE = "custom1.message";
	public static final String CUSTOM2_MESSAGE = "custom2.message";
	public static final String CUSTOM3_MESSAGE = "custom3.message";
	
	// general constants
	public static final String SUCCESS = "SUCCESS";
	public static final String ERROR = "ERROR";	
	public static final String INVALID_ACCESS = "error.invalidAccess";
	public static final String EXCEPTION = "exception";
	public static final String NONE = "none";
	
} public class Jiffy  extends Properties
{
	// @X-JVM-safe this member is ready only
	// @Thread-safe this member is read only
	private static Jiffy _jiffy;
	
	private Jiffy() 
	{
	}	
		
	public static synchronized void configure() throws Exception
	{
		if (_jiffy == null)
		{
			_jiffy = new Jiffy();
		}
		init("/jiffy.properties");
		init("/environment.properties");
		init("/" + Jiffy.getValue("environment") + ".properties");
	}
	
	private static void init(String propName) throws Exception 
	{
		_jiffy.load(Jiffy.class.getResourceAsStream(propName));
	}
	
	public static String getValue(String key)
	{
		return _jiffy.getProperty(key);
	}
	
	public static int getInt(String key)
	{
		return Integer.parseInt(_jiffy.getProperty(key));
	}
	
	public static boolean getBool(String key)
	{
		return Boolean.parseBoolean(_jiffy.getProperty(key));
	}
} public class LogUtil 
{
	public static String tabs(int count, int tabSize)
	{
		StringBuffer buf = new StringBuffer();
		for (int ct = 0; ct < (count * tabSize); ct++)
			buf.append(' ');
		return buf.toString();
	}

	public static String tabs(int count)
	{
		return tabs(count, 4);
	}
	
	public static String getStackTrace(Throwable t)
	{
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		PrintWriter pw = new PrintWriter(bytes);
		t.printStackTrace(pw);
		pw.close();
		return bytes.toString();
	}

	public static void printErrorDetails(Logger logger, Exception ex)
	{
		printErrorDetails(logger, ex, "");
	}

	public static void printErrorDetails(Logger logger, Exception ex, String customMessage)
	{
		if (!StringUtils.isEmpty(customMessage))
			logger.error(customMessage);
		
		logger.error(ex.getMessage());
		int size = Math.min(10, ex.getStackTrace().length);
		for (int i = 0; i < size; i++)
			logger.error("\t" + ex.getStackTrace()[i]);
	}

} public final class PasswordUtil  {

	public static final String THREE_OF_FOUR = "three_of_four";
	public static final String ALPHA_NUMERIC = "alpha_numeric";
	public static final String ALPHA = "alpha";
	public static final String NUMERIC = "numeric";	
	public static final String UPPER = "upper";	
	public static final String LOWER = "lower";		
	public static final String ALL = "all";
	
	public static final String ACCEPTABLE_SYMBOLS = "!@#$%*";
	
    public static String encrypt(String plaintext) throws Exception  
    {
    	String salt = "JF*FW&";
    	plaintext = plaintext + salt;
        MessageDigest md = MessageDigest.getInstance("SHA-384");
        md.update(plaintext.getBytes("UTF-8"));
        byte raw[] = md.digest(); 
        String hash = (new BASE64Encoder()).encode(raw);
        return hash;
    }
    
    public static String weakEncrypt(String plaintext) throws Exception
    {
    	String salt = "gj";
    	plaintext = plaintext + salt;
        MessageDigest md = MessageDigest.getInstance("SHA");
        md.update(plaintext.getBytes("UTF-8"));
        byte raw[] = md.digest(); 
        String hash = (new BASE64Encoder()).encode(raw);
        return hash;
    }
    
    public static String weakEncryptHTMLSafe(String plaintext) throws Exception
    {
    	String encrypted = weakEncrypt(plaintext);
    	encrypted = StringUtils.replace(encrypted, "=", "");
    	encrypted = StringUtils.replace(encrypted, "/", "");
    	encrypted = StringUtils.replace(encrypted, "\\", "");
    	encrypted = StringUtils.replace(encrypted, "&", "");
    	encrypted = StringUtils.replace(encrypted, "?", "");  
    	encrypted = StringUtils.replace(encrypted, "+", "");    	
    	return encrypted;
    }
        
    public static boolean doesPasswordMeetRules(String rule, String password)
    {
		if (StringUtils.equals(rule,  THREE_OF_FOUR))
			return checkThreeOfFour(password);
		
    	for (int i=0; i<password.length(); i++)
    	{
    		char c = password.charAt(i);
			if (StringUtils.equals(rule, ALPHA_NUMERIC) && (c<=47 || (c>=58 && c<=64) || (c>=91 && c<=96)))
				return false;
			if (StringUtils.equals(rule, ALPHA) && (c<=64 || (c>=91 && c<=96)))
				return false;
			if (StringUtils.equals(rule, NUMERIC) && (c<=47 || c>=58))
				return false;
			if (StringUtils.equals(rule, UPPER) && (c<=64 || c>=91))
				return false;
			if (StringUtils.equals(rule, LOWER) && (c<=96 || c>=123))
				return false;
    	}
    	return true;
    }
    
	public static String autogeneratePassword(int length, String rule)
	{
		StringBuilder b = new StringBuilder("");
		while (b.length()<length)
		{
			int c = (int)(Math.random()*122);
			
			// no 0's or o's or O's
			if (c == 48 || c == 79 || c == 111)
				continue;
			
			// no 1's or l's or I's
			if (c == 49 || c == 108 || c == 73)
				continue;
			
			if (StringUtils.equals(rule, ALPHA_NUMERIC) && (c<=47 || (c>=58 && c<=64) || (c>=91 && c<=96)))
				continue;
			else if (StringUtils.equals(rule, ALPHA) && (c<=64 || (c>=91 && c<=96)))
				continue;
			else if (StringUtils.equals(rule, NUMERIC) && (c<=47 || c>=58))
				continue;
			else if (StringUtils.equals(rule, UPPER) && (c<=64 || c>=91))
				continue;
			else if (StringUtils.equals(rule, LOWER) && (c<=96 || c>=123))
				continue;
			else if (StringUtils.equals(rule,  THREE_OF_FOUR) && !( (c>47 && c<58) || (c>64 && c<91) || (c>96) || c==33 || c==64 || c==35 || c==36 || c==37 || c==42) )
				continue;
			b.append((char)c);
		}
		return b.toString();
	}
	
	private static boolean checkThreeOfFour(String password)
	{
		int count = 0;
		for (int i=0; i<password.length(); i++)
		{
			char c = password.charAt(i);
			if (CharUtils.isAsciiAlphaLower(c))
			{
				count++;
				break;
			}
		}
		for (int i=0; i<password.length(); i++)
		{
			char c = password.charAt(i);
			if (CharUtils.isAsciiAlphaUpper(c))
			{
				count++;
				break;
			}
		}
		for (int i=0; i<password.length(); i++)
		{
			char c = password.charAt(i);
			if (CharUtils.isAsciiNumeric(c))
			{
				count++;
				break;
			}
		}
		for (int i=0; i<password.length(); i++)
		{
			String c = "" + password.charAt(i);
			if (StringUtils.containsOnly(c, ACCEPTABLE_SYMBOLS))
			{
				count++;
				break;
			}
		}
		if (count >=3)
			return true;
		else
			return false;
	}

} public class Text
{
	private static ResourceBundle text;
	
	private Text() {}
	
	public static synchronized ResourceBundle init()
	{
		if (text == null)
		{
			text = ResourceBundle.getBundle(Jiffy.getValue("textResources"));
		}
		return text;
	}
	
	public static String get(String key)
	{
		return text.getString(key);
	}

}
import org.apache.commons.lang3.StringUtils;
import org.jiffy.models.JiffyUser;
import org.jiffy.models.UserSession;
import org.jiffy.server.Flash;
import org.jiffy.server.Sessions;
import org.jiffy.server.security.Roles;
import org.jiffy.server.services.Service;
import org.jiffy.server.services.ServiceRequest;
import org.jiffy.server.services.ServiceResponse;
import org.jiffy.server.services.responses.HtmlResponse;
import org.jiffy.util.Constants;
import org.jiffy.util.Jiffy;
import org.jiffy.util.LogUtil;
import org.jiffy.util.PasswordUtil;
import org.jiffy.util.Text;
package org.jiffy.controllers;


public class UserSessionController extends JiffyController
{		
	@Service(access=Roles.ALL)
	public static ServiceResponse login(ServiceRequest input)