From 6fd8f7eff727517508f74b1e73c9e5a800905949 Mon Sep 17 00:00:00 2001 From: Miguel Reboiro-Jato Date: Sat, 9 Feb 2019 21:59:11 +0100 Subject: [PATCH] Changes authentication to HTTP Basic The user authentication was currently done using a cookie and following the same style of token as the HTTP Basic authentication (i.e. a concatenation of user and password joined with a ':' encoded with base64). This authentication has been replaced by a standard HTTP Basic authentication. --- db/mysql-with-inserts.sql | 7 +- db/mysql.sql | 1 + pom.xml | 73 +++--- .../java/es/uvigo/esei/daa/LoginFilter.java | 230 ------------------ .../java/es/uvigo/esei/daa/dao/UsersDAO.java | 8 +- .../java/es/uvigo/esei/daa/entities/User.java | 22 +- .../es/uvigo/esei/daa/rest/UsersResource.java | 18 +- src/main/webapp/WEB-INF/web.xml | 46 +++- src/main/webapp/css/login.css | 6 +- src/main/webapp/index.html | 64 +++-- src/main/webapp/js/dao/people.js | 92 +++---- src/main/webapp/js/login.js | 21 ++ src/main/webapp/main.html | 76 +++--- .../esei/daa/DAAExampleTestApplication.java | 22 ++ .../uvigo/esei/daa/dataset/UsersDataset.java | 4 +- .../esei/daa/filters/AuthorizationFilter.java | 101 ++++++++ .../esei/daa/rest/UsersResourceTest.java | 21 +- .../es/uvigo/esei/daa/web/PeopleWebTest.java | 20 +- src/test/resources/datasets/dataset-add.xml | 4 +- .../resources/datasets/dataset-delete.xml | 4 +- .../resources/datasets/dataset-modify.xml | 4 +- src/test/resources/datasets/dataset.dtd | 1 + src/test/resources/datasets/dataset.xml | 4 +- src/test/resources/db/hsqldb.sql | 1 + tomcat/server.hsqldb.xml | 162 ++++++++++++ tomcat/server.mysql.xml | 167 +++++++++++++ 26 files changed, 776 insertions(+), 403 deletions(-) delete mode 100644 src/main/java/es/uvigo/esei/daa/LoginFilter.java create mode 100644 src/main/webapp/js/login.js create mode 100644 src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java create mode 100644 src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java create mode 100644 tomcat/server.hsqldb.xml create mode 100644 tomcat/server.mysql.xml diff --git a/db/mysql-with-inserts.sql b/db/mysql-with-inserts.sql index d420e40..a35a952 100644 --- a/db/mysql-with-inserts.sql +++ b/db/mysql-with-inserts.sql @@ -10,6 +10,7 @@ CREATE TABLE `daaexample`.`people` ( CREATE TABLE `daaexample`.`users` ( `login` varchar(100) NOT NULL, `password` varchar(64) NOT NULL, + `role` varchar(10) NOT NULL, PRIMARY KEY (`login`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; @@ -25,5 +26,7 @@ INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Alba','Fern INSERT INTO `daaexample`.`people` (`id`,`name`,`surname`) VALUES (0,'Asunción','Jiménez'); -- The password for each user is its login suffixed with "pass". For example, user "admin" has the password "adminpass". -INSERT INTO `daaexample`.`users` (`login`,`password`) VALUES ('admin', '43f413b773f7d0cfad0e8e6529ec1249ce71e8697919eab30d82d800a3986b70'); -INSERT INTO `daaexample`.`users` (`login`,`password`) VALUES ('normal', '688f21dd2d65970f174e2c9d35159250a8a23e27585452683db8c5d10b586336'); +INSERT INTO `daaexample`.`users` (`login`,`password`,`role`) +VALUES ('admin', '713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca','ADMIN'); +INSERT INTO `daaexample`.`users` (`login`,`password`,`role`) +VALUES ('normal', '7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83','USER'); diff --git a/db/mysql.sql b/db/mysql.sql index 459a52c..ed7cd93 100644 --- a/db/mysql.sql +++ b/db/mysql.sql @@ -10,6 +10,7 @@ CREATE TABLE `daaexample`.`people` ( CREATE TABLE `daaexample`.`users` ( `login` varchar(100) NOT NULL, `password` varchar(64) NOT NULL, + `role` varchar(10) NOT NULL, PRIMARY KEY (`login`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/pom.xml b/pom.xml index 57fc0f5..5a27e31 100644 --- a/pom.xml +++ b/pom.xml @@ -1,10 +1,11 @@ - 4.0.0 es.uvigo.esei.daa example war - 0.1.9 + 0.1.10 DAA Example @@ -26,7 +27,7 @@ true 8.5.27 6300 - + 3.1.0 2.25 @@ -37,13 +38,13 @@ 4.12 2.0.0.0 3.5.1 - 3.8.1 + 3.141.59 4.3.14.RELEASE 2.5.1 1.3.0 2.3.3 5.1.45 - v0.19.1 + v0.24.0 2.4.2 @@ -320,7 +321,7 @@ - + @@ -410,7 +411,7 @@
localhost
daatestdb sa - + @@ -454,6 +455,12 @@ ${project.build.directory}/catalina-base + + + tomcat/server.hsqldb.xml + conf/server.xml + + ${jacoco.agent.itArgLine},output=tcpserver,port=${jacoco.port} -Drunmode=TEST @@ -553,6 +560,12 @@ ${project.build.directory}/catalina-base + + + tomcat/server.mysql.xml + conf/server.xml + + 9080 @@ -597,14 +610,15 @@ download-geckodriver process-test-resources - + - - - - + + + + run @@ -640,13 +654,15 @@ download-geckodriver process-test-resources - + - - - - + + + + run @@ -735,14 +751,15 @@ download-geckodriver process-test-resources - - - - - - + + + + + + run diff --git a/src/main/java/es/uvigo/esei/daa/LoginFilter.java b/src/main/java/es/uvigo/esei/daa/LoginFilter.java deleted file mode 100644 index 0e5c102..0000000 --- a/src/main/java/es/uvigo/esei/daa/LoginFilter.java +++ /dev/null @@ -1,230 +0,0 @@ -package es.uvigo.esei.daa; - -import static java.util.Objects.requireNonNull; - -import java.io.IOException; -import java.util.Base64; -import java.util.Optional; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.annotation.WebFilter; -import javax.servlet.http.Cookie; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import es.uvigo.esei.daa.dao.DAOException; -import es.uvigo.esei.daa.dao.UsersDAO; - -/** - * Security filter that implements a login protocol based on the HTTP Basic - * Authentication protocol. In this case, the login and password can be provided - * as plain request parameters or as a cookie named "token" that should contain - * both values in the same format as HTTP Basic Authentication - * ({@code base64(login + ":" + password)}). - * - * @author Miguel Reboiro Jato - * - */ -@WebFilter(urlPatterns = "/*") -public class LoginFilter implements Filter { - private static final String LOGOUT_PATH = "/logout"; - private static final String REST_PATH = "/rest"; - private static final String[] PUBLIC_PATHS = new String[] { - "/index.html", "/js", "/css" // Add the paths that can be publicly accessed (e.g. /images...) - }; - - @Override - public void doFilter( - ServletRequest request, - ServletResponse response, - FilterChain chain - ) throws IOException, ServletException { - final HttpServletRequest httpRequest = (HttpServletRequest) request; - final HttpServletResponse httpResponse = (HttpServletResponse) response; - - try { - if (isLogoutPath(httpRequest)) { - destroySession(httpRequest); - removeTokenCookie(httpRequest, httpResponse); - redirectToIndex(httpRequest, httpResponse); - } else if (isPublicPath(httpRequest) || checkToken(httpRequest)) { - chain.doFilter(request, response); - } else if (checkLogin(httpRequest, httpResponse)) { - continueWithRedirect(httpRequest, httpResponse); - } else if (isRestPath(httpRequest)) { - destroySession(httpRequest); - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - } else { - destroySession(httpRequest); - redirectToIndex(httpRequest, httpResponse); - } - } catch (IllegalArgumentException iae) { - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - } catch (DAOException e) { - httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - } - } - - @Override - public void init(FilterConfig config) throws ServletException { - } - - @Override - public void destroy() { - } - - private boolean isLogoutPath(HttpServletRequest request) { - return request.getServletPath().equals(LOGOUT_PATH); - } - - private boolean isPublicPath(HttpServletRequest request) { - for (String path : PUBLIC_PATHS) { - if (request.getServletPath().startsWith(path)) - return true; - } - - return false; - } - - private boolean isRestPath(HttpServletRequest request) { - return request.getServletPath().startsWith(REST_PATH); - } - - private void redirectToIndex( - HttpServletRequest request, - HttpServletResponse response - ) throws IOException { - response.sendRedirect(request.getContextPath()); - } - - private void continueWithRedirect( - HttpServletRequest request, - HttpServletResponse response - ) throws IOException { - String redirectPath = request.getRequestURI(); - if (request.getQueryString() != null) - redirectPath += "?" + request.getQueryString(); - - response.sendRedirect(redirectPath); - } - - private void removeTokenCookie( - HttpServletRequest request, - HttpServletResponse response - ) { - final Cookie cookie = getTokenCookie(request); - if (cookie != null) { - cookie.setMaxAge(0); - response.addCookie(cookie); - } - } - - private void destroySession(HttpServletRequest request) { - request.getSession().invalidate(); - } - - private boolean checkLogin( - HttpServletRequest request, - HttpServletResponse response - ) throws DAOException { - final String login = request.getParameter("login"); - final String password = request.getParameter("password"); - - if (login != null && password != null) { - final UsersDAO dao = new UsersDAO(); - - if (dao.checkLogin(login, password)) { - final Credentials credentials = new Credentials(login, password); - - response.addCookie(new Cookie("token", credentials.toToken())); - request.getSession().setAttribute("login", login); - - return true; - } else { - return false; - } - } else { - return false; - } - } - - private Cookie getTokenCookie(HttpServletRequest request) { - final Cookie[] cookies = Optional.ofNullable(request.getCookies()) - .orElse(new Cookie[0]); - - for (Cookie cookie : cookies) { - if ("token".equals(cookie.getName())) { - return cookie; - } - } - - return null; - } - - private boolean checkToken(HttpServletRequest request) - throws DAOException, IllegalArgumentException { - final Cookie cookie = getTokenCookie(request); - - if (cookie != null) { - final Credentials credentials = new Credentials(cookie.getValue()); - - final UsersDAO dao = new UsersDAO(); - - if (dao.checkLogin(credentials.getLogin(), credentials.getPassword())) { - request.getSession().setAttribute("login", credentials.getLogin()); - return true; - } else { - return false; - } - } - - return false; - } - - private static class Credentials { - private final String login; - private final String password; - - public Credentials(String token) { - final String decodedToken = decodeBase64(token); - final int colonIndex = decodedToken.indexOf(':'); - - if (colonIndex < 0 || colonIndex == decodedToken.length()-1) { - throw new IllegalArgumentException("Invalid token"); - } - - this.login = decodedToken.substring(0, colonIndex); - this.password = decodedToken.substring(colonIndex + 1); - } - - public Credentials(String login, String password) { - this.login = requireNonNull(login, "Login can't be null"); - this.password = requireNonNull(password, "Password can't be null"); - } - - public String getLogin() { - return login; - } - - public String getPassword() { - return password; - } - - public String toToken() { - return encodeBase64(this.login + ":" + this.password); - } - - private final static String decodeBase64(String text) { - return new String(Base64.getDecoder().decode(text.getBytes())); - } - - private final static String encodeBase64(String text) { - return Base64.getEncoder().encodeToString(text.getBytes()); - } - } -} diff --git a/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java b/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java index e73ca5a..70d47b2 100644 --- a/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java +++ b/src/main/java/es/uvigo/esei/daa/dao/UsersDAO.java @@ -19,9 +19,6 @@ import es.uvigo.esei.daa.entities.User; public class UsersDAO extends DAO { private final static Logger LOG = Logger.getLogger(UsersDAO.class.getName()); - // Yes, SALT should come from external configuration (for example, a property in Tomcat's context). - private final static String SALT = "daaexample-"; - /** * Returns a user stored persisted in the system. * @@ -70,7 +67,7 @@ public class UsersDAO extends DAO { final User user = this.get(login); final String dbPassword = user.getPassword(); - final String shaPassword = encodeSha256(SALT + password); + final String shaPassword = encodeSha256(password); return shaPassword.equals(dbPassword); } catch (IllegalArgumentException iae) { @@ -103,7 +100,8 @@ public class UsersDAO extends DAO { private User rowToEntity(ResultSet result) throws SQLException { return new User( result.getString("login"), - result.getString("password") + result.getString("password"), + result.getString("role") ); } } diff --git a/src/main/java/es/uvigo/esei/daa/entities/User.java b/src/main/java/es/uvigo/esei/daa/entities/User.java index ade776c..cb13b8e 100644 --- a/src/main/java/es/uvigo/esei/daa/entities/User.java +++ b/src/main/java/es/uvigo/esei/daa/entities/User.java @@ -10,6 +10,7 @@ import static java.util.Objects.requireNonNull; public class User { private String login; private String password; + private String role; // Constructor needed for the JSON conversion User() {} @@ -21,9 +22,10 @@ public class User { * @param password password of the user encoded using SHA-256 and with the * "salt" prefix added. */ - public User(String login, String password) { + public User(String login, String password, String role) { this.setLogin(login); this.setPassword(password); + this.setRole(role); } /** @@ -65,4 +67,22 @@ public class User { this.password = password; } + + /** + * Returns the role of the user. + * + * @return the role of the user. + */ + public String getRole() { + return role; + } + + /** + * Sets the role of the user. + * + * @param role the role of the user + */ + public void setRole(String role) { + this.role = requireNonNull(role, "Role can't be null"); + } } diff --git a/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java b/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java index 7693f5b..858c52c 100644 --- a/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java +++ b/src/main/java/es/uvigo/esei/daa/rest/UsersResource.java @@ -3,7 +3,6 @@ package es.uvigo.esei.daa.rest; import java.util.logging.Level; import java.util.logging.Logger; -import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @@ -11,6 +10,7 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import javax.ws.rs.core.SecurityContext; import es.uvigo.esei.daa.dao.DAOException; import es.uvigo.esei.daa.dao.UsersDAO; @@ -26,9 +26,7 @@ public class UsersResource { private final UsersDAO dao; - // The HttpServletRequest can be also injected as a parameter in the REST - // methods. - private @Context HttpServletRequest request; + private @Context SecurityContext security; /** * Constructs a new instance of {@link UsersResource}. @@ -43,9 +41,9 @@ public class UsersResource { } // Needed for testing purposes - UsersResource(UsersDAO dao, HttpServletRequest request) { + UsersResource(UsersDAO dao, SecurityContext security) { this.dao = dao; - this.request = request; + this.security = security; } /** @@ -71,7 +69,7 @@ public class UsersResource { // Each user can only access his or her own data. Only the admin user // can access the data of any user. - if (loggedUser.equals(login) || loggedUser.equals("admin")) { + if (loggedUser.equals(login) || this.isAdmin()) { try { return Response.ok(dao.get(login)).build(); } catch (IllegalArgumentException iae) { @@ -93,6 +91,10 @@ public class UsersResource { } private String getLogin() { - return (String) request.getSession().getAttribute("login"); + return this.security.getUserPrincipal().getName(); + } + + private boolean isAdmin() { + return this.security.isUserInRole("ADMIN"); } } diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index f75cc40..df238ce 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -5,8 +5,52 @@ http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> DAAExample - + index.html + + + + Protected Area + /rest/* + PUT + DELETE + GET + POST + OPTIONS + + + ADMIN + USER + + + + + + Admin Area + /rest/people/* + PUT + DELETE + GET + POST + OPTIONS + + + ADMIN + + + + + + ADMIN + + + USER + + + + BASIC + DAAExample + \ No newline at end of file diff --git a/src/main/webapp/css/login.css b/src/main/webapp/css/login.css index 68839b9..d6b2a48 100644 --- a/src/main/webapp/css/login.css +++ b/src/main/webapp/css/login.css @@ -16,14 +16,14 @@ body { padding-bottom: 40px; } -.form-signin { +#form-signin { width: 100%; max-width: 330px; padding: 15px; margin: 0 auto; } -.form-signin .form-control { +#form-signin .form-control { position: relative; box-sizing: border-box; height: auto; @@ -31,7 +31,7 @@ body { font-size: 16px; } -.form-signin .form-control:focus { +#form-signin .form-control:focus { z-index: 2; } diff --git a/src/main/webapp/index.html b/src/main/webapp/index.html index 0832a8f..0958229 100644 --- a/src/main/webapp/index.html +++ b/src/main/webapp/index.html @@ -1,25 +1,45 @@ - - - - - DAA Example - Login - - - - - -
-

DAA Example

- - - - - - - - -
- + + + + +DAA Example - Login + + + + + +
+

DAA Example

+ + + + + +
+ + + + + + \ No newline at end of file diff --git a/src/main/webapp/js/dao/people.js b/src/main/webapp/js/dao/people.js index 281bdc2..29618ee 100644 --- a/src/main/webapp/js/dao/people.js +++ b/src/main/webapp/js/dao/people.js @@ -1,47 +1,51 @@ var PeopleDAO = (function() { - var resourcePath = "rest/people/"; - var requestByAjax = function(data, done, fail, always) { - done = typeof done !== 'undefined' ? done : function() {}; - fail = typeof fail !== 'undefined' ? fail : function() {}; - always = typeof always !== 'undefined' ? always : function() {}; - - $.ajax(data) - .done(done) - .fail(fail) - .always(always); - }; - - function PeopleDAO() { - this.listPeople = function(done, fail, always) { - requestByAjax({ - url: resourcePath, - type: 'GET' - }, done, fail, always); - }; - - this.addPerson = function(person, done, fail, always) { - requestByAjax({ - url: resourcePath, - type: 'POST', - data: person - }, done, fail, always); - }; - - this.modifyPerson = function(person, done, fail, always) { - requestByAjax({ - url: resourcePath + person.id, - type: 'PUT', - data: person - }, done, fail, always); - }; - - this.deletePerson = function(id, done, fail, always) { - requestByAjax({ - url: resourcePath + id, - type: 'DELETE', - }, done, fail, always); - }; + var resourcePath = "rest/people/"; + var requestByAjax = function(data, done, fail, always) { + done = typeof done !== 'undefined' ? done : function() {}; + fail = typeof fail !== 'undefined' ? fail : function() {}; + always = typeof always !== 'undefined' ? always : function() {}; + + let authToken = localStorage.getItem('authorization-token'); + if (authToken !== null) { + data.beforeSend = function(xhr) { + xhr.setRequestHeader('Authorization', 'Basic ' + authToken); + }; } - - return PeopleDAO; + + $.ajax(data).done(done).fail(fail).always(always); + }; + + function PeopleDAO() { + this.listPeople = function(done, fail, always) { + requestByAjax({ + url : resourcePath, + type : 'GET' + }, done, fail, always); + }; + + this.addPerson = function(person, done, fail, always) { + requestByAjax({ + url : resourcePath, + type : 'POST', + data : person + }, done, fail, always); + }; + + this.modifyPerson = function(person, done, fail, always) { + requestByAjax({ + url : resourcePath + person.id, + type : 'PUT', + data : person + }, done, fail, always); + }; + + this.deletePerson = function(id, done, fail, always) { + requestByAjax({ + url : resourcePath + id, + type : 'DELETE', + }, done, fail, always); + }; + } + + return PeopleDAO; })(); \ No newline at end of file diff --git a/src/main/webapp/js/login.js b/src/main/webapp/js/login.js new file mode 100644 index 0000000..585c1dd --- /dev/null +++ b/src/main/webapp/js/login.js @@ -0,0 +1,21 @@ +function doLogin(login, password) { + $.ajax({ + url: 'rest/users/' + login, + type: 'GET', + beforeSend: function (xhr) { + xhr.setRequestHeader('Authorization', 'Basic ' + btoa(login + ":" + password)); + } + }) + .done(function() { + localStorage.setItem('authorization-token', btoa(login + ":" + password)); + window.location = 'main.html'; + }) + .fail(function() { + alert('Invalid login and/or password.'); + }); +} + +function doLogout() { + localStorage.removeItem('authorization-token'); + window.location = 'index.html'; +} \ No newline at end of file diff --git a/src/main/webapp/main.html b/src/main/webapp/main.html index 7435754..c2e28f1 100644 --- a/src/main/webapp/main.html +++ b/src/main/webapp/main.html @@ -1,40 +1,50 @@ - - - - DAA Example - - - - -
- -
+ + + +DAA Example -
-
-

Personas

+ + + +
+ +
+ +
+
+

Personas

+
+
+ + + + + + - - - - + view.init(); + }); + + \ No newline at end of file diff --git a/src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java b/src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java new file mode 100644 index 0000000..43b5809 --- /dev/null +++ b/src/test/java/es/uvigo/esei/daa/DAAExampleTestApplication.java @@ -0,0 +1,22 @@ +package es.uvigo.esei.daa; + +import static java.util.Collections.unmodifiableSet; + +import java.util.HashSet; +import java.util.Set; + +import javax.ws.rs.ApplicationPath; + +import es.uvigo.esei.daa.filters.AuthorizationFilter; + +@ApplicationPath("/rest/*") +public class DAAExampleTestApplication extends DAAExampleApplication { + @Override + public Set> getClasses() { + final Set> classes = new HashSet<>(super.getClasses()); + + classes.add(AuthorizationFilter.class); + + return unmodifiableSet(classes); + } +} diff --git a/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java b/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java index 44082c7..82cbcde 100644 --- a/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java +++ b/src/test/java/es/uvigo/esei/daa/dataset/UsersDataset.java @@ -10,8 +10,8 @@ public final class UsersDataset { public static User[] users() { return new User[] { - new User(adminLogin(), "43f413b773f7d0cfad0e8e6529ec1249ce71e8697919eab30d82d800a3986b70"), - new User(normalLogin(), "688f21dd2d65970f174e2c9d35159250a8a23e27585452683db8c5d10b586336") + new User(adminLogin(), "713bfda78870bf9d1b261f565286f85e97ee614efe5f0faf7c34e7ca4f65baca", "ADMIN"), + new User(normalLogin(), "7bf24d6ca2242430343ab7e3efb89559a47784eea1123be989c1b2fb2ef66e83", "USER") }; } diff --git a/src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java b/src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java new file mode 100644 index 0000000..6c058a1 --- /dev/null +++ b/src/test/java/es/uvigo/esei/daa/filters/AuthorizationFilter.java @@ -0,0 +1,101 @@ +package es.uvigo.esei.daa.filters; + +import java.io.IOException; +import java.security.Principal; +import java.util.Base64; + +import javax.annotation.Priority; +import javax.ws.rs.Priorities; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ContainerRequestFilter; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.Status; +import javax.ws.rs.core.SecurityContext; +import javax.ws.rs.ext.Provider; + +import es.uvigo.esei.daa.dao.DAOException; +import es.uvigo.esei.daa.dao.UsersDAO; +import es.uvigo.esei.daa.entities.User; + +@Provider +@Priority(Priorities.AUTHENTICATION) +public class AuthorizationFilter implements ContainerRequestFilter { + private final UsersDAO dao; + + public AuthorizationFilter() { + this.dao = new UsersDAO(); + } + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + // Get the authentication passed in HTTP headers parameters + final String auth = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION); + + if (auth == null) { + requestContext.abortWith(createResponse()); + } else { + final byte[] decodedToken = Base64.getDecoder() + .decode(auth.substring(6)); + + final String userColonPass = new String(decodedToken); + final String[] userPass = userColonPass.split(":", 2); + + if (userPass.length == 2) { + try { + if (this.dao.checkLogin(userPass[0], userPass[1])) { + final User user = this.dao.get(userPass[0]); + + requestContext.setSecurityContext(new UserSecurityContext(user)); + } else { + requestContext.abortWith(createResponse()); + } + } catch (DAOException e) { + requestContext.abortWith(createResponse()); + } + } else { + requestContext.abortWith(createResponse()); + } + } + } + + private static Response createResponse() { + return Response.status(Status.UNAUTHORIZED) + .header(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"DAAExample\"") + .entity("Page requires login.") + .build(); + } + + private static final class UserSecurityContext implements SecurityContext { + private final User user; + + private UserSecurityContext(User user) { + this.user = user; + } + + @Override + public boolean isUserInRole(String role) { + return user.getRole().equals(role); + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public Principal getUserPrincipal() { + return new Principal() { + @Override + public String getName() { + return user.getLogin(); + } + }; + } + + @Override + public String getAuthenticationScheme() { + return SecurityContext.BASIC_AUTH; + } + } +} diff --git a/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java b/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java index 042faec..ed9a674 100644 --- a/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java +++ b/src/test/java/es/uvigo/esei/daa/rest/UsersResourceTest.java @@ -4,7 +4,6 @@ import static es.uvigo.esei.daa.dataset.UsersDataset.adminLogin; import static es.uvigo.esei.daa.dataset.UsersDataset.normalLogin; import static es.uvigo.esei.daa.dataset.UsersDataset.user; import static es.uvigo.esei.daa.dataset.UsersDataset.userToken; -import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasForbidden; import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasOkStatus; import static es.uvigo.esei.daa.matchers.HasHttpStatus.hasUnauthorized; import static es.uvigo.esei.daa.matchers.IsEqualToUser.equalsToUser; @@ -36,8 +35,7 @@ import com.github.springtestdbunit.DbUnitTestExecutionListener; import com.github.springtestdbunit.annotation.DatabaseSetup; import com.github.springtestdbunit.annotation.ExpectedDatabase; -import es.uvigo.esei.daa.DAAExampleApplication; -import es.uvigo.esei.daa.LoginFilter; +import es.uvigo.esei.daa.DAAExampleTestApplication; import es.uvigo.esei.daa.entities.User; import es.uvigo.esei.daa.listeners.ApplicationContextBinding; import es.uvigo.esei.daa.listeners.ApplicationContextJndiBindingTestExecutionListener; @@ -64,7 +62,7 @@ import es.uvigo.esei.daa.listeners.DbManagementTestExecutionListener; public class UsersResourceTest extends JerseyTest { @Override protected Application configure() { - return new DAAExampleApplication(); + return new DAAExampleTestApplication(); } @Override @@ -87,7 +85,6 @@ public class UsersResourceTest extends JerseyTest { new ServletContainer(ResourceConfig.forApplication(configure())) ) .servletPath("/rest") - .addFilter(LoginFilter.class, "login-filter") .build(); } @@ -96,7 +93,7 @@ public class UsersResourceTest extends JerseyTest { final String admin = adminLogin(); final Response response = target("users/" + admin).request() - .cookie("token", userToken(admin)) + .header("Authorization", "Basic " + userToken(admin)) .get(); assertThat(response, hasOkStatus()); @@ -111,7 +108,7 @@ public class UsersResourceTest extends JerseyTest { final String otherUser = normalLogin(); final Response response = target("users/" + otherUser).request() - .cookie("token", userToken(admin)) + .header("Authorization", "Basic " + userToken(admin)) .get(); assertThat(response, hasOkStatus()); @@ -125,7 +122,7 @@ public class UsersResourceTest extends JerseyTest { final String login = normalLogin(); final Response response = target("users/" + login).request() - .cookie("token", userToken(login)) + .header("Authorization", "Basic " + userToken(login)) .get(); assertThat(response, hasOkStatus()); @@ -138,22 +135,22 @@ public class UsersResourceTest extends JerseyTest { public void testGetNoCredentials() throws IOException { final Response response = target("users/" + normalLogin()).request().get(); - assertThat(response, hasForbidden()); + assertThat(response, hasUnauthorized()); } @Test public void testGetBadCredentials() throws IOException { final Response response = target("users/" + adminLogin()).request() - .cookie("token", "YmFkOmNyZWRlbnRpYWxz") + .header("Authorization", "Basic YmFkOmNyZWRlbnRpYWxz") .get(); - assertThat(response, hasForbidden()); + assertThat(response, hasUnauthorized()); } @Test public void testGetIllegalAccess() throws IOException { final Response response = target("users/" + adminLogin()).request() - .cookie("token", userToken(normalLogin())) + .header("Authorization", "Basic " + userToken(normalLogin())) .get(); assertThat(response, hasUnauthorized()); diff --git a/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java b/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java index 5c7b48b..81313c8 100644 --- a/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java +++ b/src/test/java/es/uvigo/esei/daa/web/PeopleWebTest.java @@ -20,9 +20,12 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.openqa.selenium.Cookie; import org.openqa.selenium.WebDriver; import org.openqa.selenium.firefox.FirefoxDriver; +import org.openqa.selenium.firefox.FirefoxOptions; +import org.openqa.selenium.firefox.FirefoxProfile; +import org.openqa.selenium.html5.LocalStorage; +import org.openqa.selenium.remote.DesiredCapabilities; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -64,15 +67,24 @@ public class PeopleWebTest { @Before public void setUp() throws Exception { final String baseUrl = "http://localhost:9080/DAAExample/"; + + final FirefoxProfile profile = new FirefoxProfile(); + profile.setPreference("browser.privatebrowsing.autostart", true); + + final FirefoxOptions options = new FirefoxOptions(DesiredCapabilities.firefox()); + options.setProfile(profile); - driver = new FirefoxDriver(); + final FirefoxDriver firefoxDriver; + driver = firefoxDriver = new FirefoxDriver(); driver.get(baseUrl); // Driver will wait DEFAULT_WAIT_TIME if it doesn't find and element. driver.manage().timeouts().implicitlyWait(DEFAULT_WAIT_TIME, TimeUnit.SECONDS); - + driver.manage().window().maximize(); + // Login as "admin:adminpass" - driver.manage().addCookie(new Cookie("token", "YWRtaW46YWRtaW5wYXNz")); + final LocalStorage localStorage = firefoxDriver.getLocalStorage(); + localStorage.setItem("authorization-token", "YWRtaW46YWRtaW5wYXNz"); mainPage = new MainPage(driver, baseUrl); mainPage.navigateTo(); diff --git a/src/test/resources/datasets/dataset-add.xml b/src/test/resources/datasets/dataset-add.xml index 832a4a1..9a75a99 100644 --- a/src/test/resources/datasets/dataset-add.xml +++ b/src/test/resources/datasets/dataset-add.xml @@ -14,6 +14,6 @@ - - + + \ No newline at end of file diff --git a/src/test/resources/datasets/dataset-delete.xml b/src/test/resources/datasets/dataset-delete.xml index 7480a41..e49223d 100644 --- a/src/test/resources/datasets/dataset-delete.xml +++ b/src/test/resources/datasets/dataset-delete.xml @@ -12,6 +12,6 @@ - - + + \ No newline at end of file diff --git a/src/test/resources/datasets/dataset-modify.xml b/src/test/resources/datasets/dataset-modify.xml index f840fb5..6e2dfc9 100644 --- a/src/test/resources/datasets/dataset-modify.xml +++ b/src/test/resources/datasets/dataset-modify.xml @@ -13,6 +13,6 @@ - - + + \ No newline at end of file diff --git a/src/test/resources/datasets/dataset.dtd b/src/test/resources/datasets/dataset.dtd index c09a15e..e64500f 100644 --- a/src/test/resources/datasets/dataset.dtd +++ b/src/test/resources/datasets/dataset.dtd @@ -10,4 +10,5 @@ diff --git a/src/test/resources/datasets/dataset.xml b/src/test/resources/datasets/dataset.xml index 2a7eff1..3f48cc9 100644 --- a/src/test/resources/datasets/dataset.xml +++ b/src/test/resources/datasets/dataset.xml @@ -13,6 +13,6 @@ - - + + \ No newline at end of file diff --git a/src/test/resources/db/hsqldb.sql b/src/test/resources/db/hsqldb.sql index 00d02ba..a629441 100644 --- a/src/test/resources/db/hsqldb.sql +++ b/src/test/resources/db/hsqldb.sql @@ -8,5 +8,6 @@ CREATE TABLE people ( CREATE TABLE users ( login VARCHAR(100) NOT NULL, password VARCHAR(64) NOT NULL, + role VARCHAR(5) NOT NULL, PRIMARY KEY (login) ); \ No newline at end of file diff --git a/tomcat/server.hsqldb.xml b/tomcat/server.hsqldb.xml new file mode 100644 index 0000000..64c6e65 --- /dev/null +++ b/tomcat/server.hsqldb.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tomcat/server.mysql.xml b/tomcat/server.mysql.xml new file mode 100644 index 0000000..9e09ce4 --- /dev/null +++ b/tomcat/server.mysql.xml @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- 2.18.1