diff --git a/WebContent/META-INF/context.xml b/WebContent/META-INF/context.xml new file mode 100644 index 0000000..57a1c0c --- /dev/null +++ b/WebContent/META-INF/context.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/WebContent/WEB-INF/undertow-handlers.conf b/WebContent/WEB-INF/undertow-handlers.conf new file mode 100644 index 0000000..ed74c67 --- /dev/null +++ b/WebContent/WEB-INF/undertow-handlers.conf @@ -0,0 +1 @@ +samesite-cookie(mode=Lax) \ No newline at end of file diff --git a/WebContent/index-old.jsp b/WebContent/index-old.jsp new file mode 100644 index 0000000..d7b96a7 --- /dev/null +++ b/WebContent/index-old.jsp @@ -0,0 +1,18 @@ +<%@ +page import="java.util.Collection,org.leolo.web.dm.dao.*,org.leolo.web.dm.model.*,org.leolo.web.dm.util.*" +%> + + Download Manager + + +

Download Manager

+ <% Collection projs = new ProjectDao().getAllProjects(); %> +

Project List

+ + + \ No newline at end of file diff --git a/WebContent/index.jsp b/WebContent/index.jsp index d7b96a7..c09ee74 100644 --- a/WebContent/index.jsp +++ b/WebContent/index.jsp @@ -1,18 +1,100 @@ -<%@ -page import="java.util.Collection,org.leolo.web.dm.dao.*,org.leolo.web.dm.model.*,org.leolo.web.dm.util.*" -%> - - Download Manager - - -

Download Manager

- <% Collection projs = new ProjectDao().getAllProjects(); %> -

Project List

- - + + + + + Download Manager + + + + + + + + +
+ +
+ + \ No newline at end of file diff --git a/src/org/leolo/web/dm/Constant.java b/src/org/leolo/web/dm/Constant.java index f25aa8e..2f5fe1d 100644 --- a/src/org/leolo/web/dm/Constant.java +++ b/src/org/leolo/web/dm/Constant.java @@ -8,11 +8,20 @@ public class Constant { //Basic user information map public static final String BUI_KEY_USER_ID = "uid"; public static final String BUI_KEY_USER_NAME = "uname"; + public static final String BUI_KEY_PASSWORD = "passwd"; + public static final String BUI_KEY_FAIL_CNT = "fail_cnt"; //System parameter map public static final String SP_DATA_DIR = "data_dir"; public static final String SP_CACHE_DIR = "cache_dir"; public static final String SP_MAX_CACHE_SIZE = "cache_size"; + public static final String SP_SYSNAME = "sys_name"; + public static final String SP_MAX_LOGIN_FAIL_CNT = "max_fail_login"; + public static final String SP_BCRYPT_COST = "bcrypt_cost"; + public static final String SP_QUEUED_JOB_THREAD = "qj_thread"; + + //System Information + public static final String SI_API_VERSION = "0.1"; //Common value public static final int COM_DEFAULT_USER_ID = 1; diff --git a/src/org/leolo/web/dm/dao/UserDao.java b/src/org/leolo/web/dm/dao/UserDao.java new file mode 100644 index 0000000..5a7ff24 --- /dev/null +++ b/src/org/leolo/web/dm/dao/UserDao.java @@ -0,0 +1,82 @@ +package org.leolo.web.dm.dao; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Hashtable; +import java.util.Map; + +import org.leolo.web.dm.Constant; +import org.leolo.web.dm.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class UserDao extends BaseDao { + + private static Logger log = LoggerFactory.getLogger(UserDao.class); + + public Map getBasicUserInfoByUsernameWithPassword(String username) { + Map u = new Hashtable<>(); + try (Connection conn = getConnection()) { + // Step 1: get basic user info + try (PreparedStatement pstmt = conn + .prepareStatement("SELECT user_id, user_name, password, pwd_err_cnt FROM user WHERE user_name = ?")) { + pstmt.setString(1, username); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + u.put(Constant.BUI_KEY_USER_NAME, rs.getString(2)); + u.put(Constant.BUI_KEY_USER_ID, rs.getInt(1)); + u.put(Constant.BUI_KEY_PASSWORD, rs.getString(3)); + u.put(Constant.BUI_KEY_FAIL_CNT, rs.getInt(4)); + } else { + return null; + } + } + } + } catch (SQLException e) { + log.error(e.getMessage(), e); + return null; + } + return u; + } + + public void updateLastLogin(int userId) { + try( + Connection conn = getConnection(); + PreparedStatement pstmt = conn.prepareStatement("UPDATE user SET pwd_err_cnt = 0, last_login=NOW() WHERE user_id = ?") + ){ + pstmt.setInt(1, userId); + pstmt.executeUpdate(); + }catch(SQLException e) { + log.error(e.getMessage(), e); + } + + } + + public void updateLoginFailedCount(int userId) { + try( + Connection conn = getConnection(); + PreparedStatement pstmt = conn.prepareStatement("UPDATE user SET pwd_err_cnt = nvl(pwd_err_cnt,0) + 1 WHERE user_id = ?") + ){ + pstmt.setInt(1, userId); + pstmt.executeUpdate(); + }catch(SQLException e) { + log.error(e.getMessage(), e); + } + } + + public void lockUser(int userId) { + try( + Connection conn = getConnection(); + PreparedStatement pstmt = conn.prepareStatement("UPDATE user SET password = concat('!!',password) WHERE user_id = ? AND password NOT LIKE '!!%'") + ){ + pstmt.setInt(1, userId); + pstmt.executeUpdate(); + + }catch(SQLException e) { + log.error(e.getMessage(), e); + } + + } +} diff --git a/src/org/leolo/web/dm/model/User.java b/src/org/leolo/web/dm/model/User.java index 4c87525..36c4cbe 100644 --- a/src/org/leolo/web/dm/model/User.java +++ b/src/org/leolo/web/dm/model/User.java @@ -1,5 +1,42 @@ package org.leolo.web.dm.model; +import java.util.HashSet; +import java.util.Set; + public class User { + + private String userName; + private int userId; + private String password; + + private Set roles = new HashSet<>(); + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + public Set getRoles() { + return roles; + } + } diff --git a/src/org/leolo/web/dm/servlet/BaseServlet.java b/src/org/leolo/web/dm/servlet/BaseServlet.java index bbf92a2..0963312 100644 --- a/src/org/leolo/web/dm/servlet/BaseServlet.java +++ b/src/org/leolo/web/dm/servlet/BaseServlet.java @@ -25,6 +25,7 @@ public class BaseServlet extends HttpServlet { protected String userName = null; protected int userId = Constant.COM_DEFAULT_USER_ID; protected boolean fatalError = false; + protected boolean identifiedByKey = false; /** * @see HttpServlet#HttpServlet() @@ -33,18 +34,22 @@ public class BaseServlet extends HttpServlet { super(); // TODO Auto-generated constructor stub } - + + private void resetParam() { + userName = null; + userId = Constant.COM_DEFAULT_USER_ID; + fatalError = false; + identifiedByKey = false; + } + /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - log.info("Request - [GET] {}", request.getRequestURI()); - //Reset parameters - userName = null; - userId = Constant.COM_DEFAULT_USER_ID; - fatalError = false; - - if(request.getAttribute(Constant.SESSION_USER_NAME)!=null) { +// log.info("Request - [GET] {}", request.getRequestURI()); + resetParam(); +// log.info("SUN : {}", request.getSession().getAttribute(Constant.SESSION_USER_NAME)); + if(request.getSession().getAttribute(Constant.SESSION_USER_NAME)!=null) { userName = request.getSession().getAttribute(Constant.SESSION_USER_NAME).toString(); userId = (Integer) request.getSession().getAttribute(Constant.SESSION_USER_ID); } @@ -56,6 +61,7 @@ public class BaseServlet extends HttpServlet { if(buim!=null) { userName = buim.get(Constant.BUI_KEY_USER_NAME).toString(); userId = (Integer) buim.get(Constant.BUI_KEY_USER_ID); + identifiedByKey = true; apiDao.markKeyUsed(key); }else { response.setContentType("application/json"); diff --git a/src/org/leolo/web/dm/servlet/LoginServlet.java b/src/org/leolo/web/dm/servlet/LoginServlet.java new file mode 100644 index 0000000..ed96a54 --- /dev/null +++ b/src/org/leolo/web/dm/servlet/LoginServlet.java @@ -0,0 +1,96 @@ +package org.leolo.web.dm.servlet; + +import java.io.IOException; +import java.util.Map; + +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONObject; +import org.leolo.web.dm.Constant; +import org.leolo.web.dm.dao.SystemParameterDao; +import org.leolo.web.dm.dao.UserDao; +import org.leolo.web.dm.util.QueuedJobs; +import org.leolo.web.dm.util.ServletUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.slf4j.Marker; +import org.slf4j.MarkerFactory; + +import at.favre.lib.crypto.bcrypt.BCrypt; + +/** + * Servlet implementation class LoginServlet + */ +@WebServlet("/Login") +public class LoginServlet extends HttpServlet { + private static Logger log = LoggerFactory.getLogger(LoginServlet.class); + private static Marker mark = MarkerFactory.getMarker("user"); + private static final long serialVersionUID = 1L; + + /** + * @see HttpServlet#HttpServlet() + */ + public LoginServlet() { + super(); + // TODO Auto-generated constructor stub + } + + /** + * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) + */ + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + // TODO Auto-generated method stub + UserDao udao = new UserDao(); + SystemParameterDao spdao = new SystemParameterDao(); + log.info("Login attempted! {}@{}:{}", request.getParameter("username"), request.getRemoteHost(), request.getParameter("password")); + Map bui = udao.getBasicUserInfoByUsernameWithPassword(request.getParameter("username")); + if(bui!=null) { + if(BCrypt.verifyer().verify(request.getParameter("password").toCharArray(), bui.get(Constant.BUI_KEY_PASSWORD).toString().toCharArray()).verified) { + log.info(mark, "User {}@{} logged in successfully!", bui.get(Constant.BUI_KEY_USER_NAME), ServletUtil.getClientIpAddr(request)); + request.getSession().setAttribute(Constant.SESSION_USER_ID, (int)bui.get(Constant.BUI_KEY_USER_ID)); + request.getSession().setAttribute(Constant.SESSION_USER_NAME, bui.get(Constant.BUI_KEY_USER_NAME)); + sendSuccessResponse(response); + QueuedJobs.getInstance().queue(()->{ + udao.updateLastLogin((int)bui.get(Constant.BUI_KEY_USER_ID)); + }); + }else { + log.info(mark, "User {}@{} attempted to login![ICP/UL]", bui.get(Constant.BUI_KEY_USER_NAME), ServletUtil.getClientIpAddr(request)); + sendFailedResponse(response); + QueuedJobs.getInstance().queue(()->{ + udao.updateLoginFailedCount((int)bui.get(Constant.BUI_KEY_USER_ID)); + int failedCount = (int) bui.get(Constant.BUI_KEY_FAIL_CNT); + failedCount++; + if(failedCount >= spdao.getInt(Constant.SP_MAX_LOGIN_FAIL_CNT, 5)) { + //lock account + udao.lockUser((int)bui.get(Constant.BUI_KEY_USER_ID)); + } + }); + } + }else { + //User does not exist! + log.info(mark, "User {}@{} attempted to login![UNE]", request.getParameter("username"), ServletUtil.getClientIpAddr(request)); + BCrypt.withDefaults().hashToString(spdao.getInt(Constant.SP_BCRYPT_COST, 12), request.getParameter("password").toCharArray()); + sendFailedResponse(response); + } + } + + private void sendFailedResponse(HttpServletResponse response) throws IOException{ + response.setContentType("application/json"); + JSONObject obj = new JSONObject(); + obj.put("status", "failed"); + obj.put("message", "Username and/or password incorrect, or account is being locked."); + obj.write(response.getWriter()); + } + + private void sendSuccessResponse(HttpServletResponse response) throws IOException{ + response.setContentType("application/json"); + JSONObject obj = new JSONObject(); + obj.put("status", "success"); + obj.write(response.getWriter()); + } + +} diff --git a/src/org/leolo/web/dm/servlet/UserInfoServlet.java b/src/org/leolo/web/dm/servlet/UserInfoServlet.java new file mode 100644 index 0000000..421d985 --- /dev/null +++ b/src/org/leolo/web/dm/servlet/UserInfoServlet.java @@ -0,0 +1,49 @@ +package org.leolo.web.dm.servlet; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.annotation.WebServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.json.JSONObject; +import org.leolo.web.dm.Constant; +import org.leolo.web.dm.dao.SystemParameterDao; + +/** + * Servlet implementation class UserInfoServlet + */ +@WebServlet("/UserInfo") +public class UserInfoServlet extends BaseServlet { + private static final long serialVersionUID = 1L; + + /** + * @see BaseServlet#BaseServlet() + */ + public UserInfoServlet() { + super(); + // TODO Auto-generated constructor stub + } + + /** + * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) + */ + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + super.doGet(request, response); + if(this.fatalError) { + return; + } + response.setContentType("application/json"); + JSONObject obj = new JSONObject(); + obj.put("status", "success"); + obj.put("identified", this.userName!=null); + if(this.userName!=null) { + obj.put("username", this.userName); + obj.put("method", identifiedByKey?"api-key":"session"); + } + obj.put("system-name", new SystemParameterDao().getString(Constant.SP_SYSNAME)); + obj.put("api-version", Constant.SI_API_VERSION); + obj.write(response.getWriter()); + } + +} diff --git a/src/org/leolo/web/dm/util/QueuedJobs.java b/src/org/leolo/web/dm/util/QueuedJobs.java new file mode 100644 index 0000000..ccc7084 --- /dev/null +++ b/src/org/leolo/web/dm/util/QueuedJobs.java @@ -0,0 +1,29 @@ +package org.leolo.web.dm.util; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +import org.leolo.web.dm.Constant; +import org.leolo.web.dm.dao.SystemParameterDao; + + +public class QueuedJobs { + private static QueuedJobs instance; + private ExecutorService pool = Executors.newFixedThreadPool(new SystemParameterDao().getInt(Constant.SP_QUEUED_JOB_THREAD, 10)); + + public static synchronized QueuedJobs getInstance() { + if(instance==null) { + instance=new QueuedJobs(); + } + return instance; + } + + private QueuedJobs() { + + } + + public void queue(Runnable r) { + pool.execute(r); + } +} diff --git a/src/org/leolo/web/dm/util/ServletUtil.java b/src/org/leolo/web/dm/util/ServletUtil.java new file mode 100644 index 0000000..282c4cf --- /dev/null +++ b/src/org/leolo/web/dm/util/ServletUtil.java @@ -0,0 +1,25 @@ +package org.leolo.web.dm.util; + +import javax.servlet.http.HttpServletRequest; + +public class ServletUtil { + public static String getClientIpAddr(HttpServletRequest request) { + String ip = request.getHeader("X-Forwarded-For"); + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("WL-Proxy-Client-IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_CLIENT_IP"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getHeader("HTTP_X_FORWARDED_FOR"); + } + if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { + ip = request.getRemoteAddr(); + } + return ip; + } +} diff --git a/src/org/leolo/web/dm/util/SystemInformation.java b/src/org/leolo/web/dm/util/SystemInformation.java new file mode 100644 index 0000000..bbe6832 --- /dev/null +++ b/src/org/leolo/web/dm/util/SystemInformation.java @@ -0,0 +1,5 @@ +package org.leolo.web.dm.util; + +public class SystemInformation { + +}