diff --git a/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/ChangePasswordController1_8.java b/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/ChangePasswordController1_8.java index abac32aea..53a93a04d 100644 --- a/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/ChangePasswordController1_8.java +++ b/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/ChangePasswordController1_8.java @@ -9,9 +9,8 @@ */ package org.openmrs.module.webservices.rest.web.v1_0.controller.openmrs1_8; -import java.util.Map; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.openmrs.User; import org.openmrs.api.APIAuthenticationException; import org.openmrs.api.APIException; @@ -27,25 +26,29 @@ import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @Controller @RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/password") public class ChangePasswordController1_8 extends BaseRestController { - + @Qualifier("userService") @Autowired private UserService userService; - + + private final Log log = LogFactory.getLog(getClass()); + @RequestMapping(method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) - public void changeOwnPassword(@RequestBody Map body) { + public void changeOwnPassword(@RequestBody Map body, HttpServletRequest servletRequest) { String oldPassword = body.get("oldPassword"); String newPassword = body.get("newPassword"); if (!Context.isAuthenticated()) { @@ -53,13 +56,16 @@ public void changeOwnPassword(@RequestBody Map body) { } try { userService.changePassword(oldPassword, newPassword); - } - catch (APIException ex) { + SessionListener.invalidateOtherSessions(Context.getAuthenticatedUser().getUuid(), servletRequest.getSession()); + } catch (APIException ex) { // this happens if they give the wrong oldPassword + log.error("Change password failed", ex); throw new ValidationException(ex.getMessage()); + } catch (Exception e) { + log.error("Change password failed", e); } } - + @RequestMapping(value = "/{userUuid}", method = RequestMethod.POST) @ResponseStatus(HttpStatus.OK) public void changeOthersPassword(@PathVariable("userUuid") String userUuid, @RequestBody Map body) { @@ -69,26 +75,71 @@ public void changeOthersPassword(@PathVariable("userUuid") String userUuid, @Req User user; try { user = userService.getUserByUuid(userUuid); - } - finally { + } finally { Context.removeProxyPrivilege(PrivilegeConstants.VIEW_USERS); Context.removeProxyPrivilege("Get Users"); } - + if (user == null || user.getUserId() == null) { throw new NullPointerException(); } else { userService.changePassword(user, newPassword); + SessionListener.invalidateAllSessions(user.getUuid()); } } - + // This probably belongs in the base class, but we don't want to test all the behaviors that would change @ExceptionHandler(NullPointerException.class) @ResponseBody public SimpleObject handleNotFound(NullPointerException exception, HttpServletRequest request, - HttpServletResponse response) { + HttpServletResponse response) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); return RestUtil.wrapErrorResponse(exception, "User not found"); } - + + static class SessionListener { + private static final Log log = LogFactory.getLog(SessionListener.class); + + private static final Map> map = new HashMap<>(); + + public static void sessionCreated(String userUuid, HttpSession httpSession) { + if (!map.containsKey(userUuid)) + map.put(userUuid, new ArrayList<>()); + + List sessions = map.get(userUuid); + if (sessions.contains(httpSession)) + return; + + sessions.add(httpSession); + log.info(String.format("Added new session. Total sessions for user: %s = %d", userUuid, map.get(userUuid).size())); + } + + public static void invalidateOtherSessions(String userUuid, HttpSession currentSession) { + log.info(String.format("Finding other sessions for the user: %s, for session: %s", userUuid, currentSession)); + List sessions = map.get(userUuid); + for (HttpSession session : sessions) { + if (!currentSession.getId().equals(session.getId())) { + session.invalidate(); + } + } + ArrayList httpSessions = new ArrayList<>(); + httpSessions.add(currentSession); + map.put(userUuid, httpSessions); + log.info(String.format("Invalidated %d other sessions for the user with this session", sessions.size() - 1)); + } + + public static void invalidateAllSessions(String userUuid) { + log.info(String.format("Finding other sessions for the user: %s", userUuid)); + + List sessions = map.get(userUuid); + if (sessions == null) { + log.info("No sessions found for this user"); + return; + } + + sessions.forEach(HttpSession::invalidate); + map.remove(userUuid); + log.info(String.format("Found %d sessions for the user: %s", sessions.size(), userUuid)); + } + } } diff --git a/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8.java b/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8.java index 1e6101a31..ee97f1de5 100644 --- a/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8.java +++ b/omod-1.8/src/main/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8.java @@ -15,12 +15,16 @@ import java.util.Set; import org.apache.commons.lang3.LocaleUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.openmrs.User; import org.openmrs.api.APIException; import org.openmrs.api.context.Context; import org.openmrs.module.webservices.rest.SimpleObject; import org.openmrs.module.webservices.rest.web.ConversionUtil; import org.openmrs.module.webservices.rest.web.RestConstants; import org.openmrs.module.webservices.rest.web.api.RestService; +import org.openmrs.module.webservices.rest.web.representation.CustomRepresentation; import org.openmrs.module.webservices.rest.web.representation.Representation; import org.openmrs.module.webservices.rest.web.v1_0.controller.BaseRestController; import org.springframework.beans.factory.annotation.Autowired; @@ -33,6 +37,8 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.context.request.WebRequest; +import javax.servlet.http.HttpServletRequest; + /** * Controller that lets a client check the status of their session, and log out. (Authenticating is * handled through a filter, and may happen through this or any other resource. @@ -40,13 +46,15 @@ @Controller @RequestMapping(value = "/rest/" + RestConstants.VERSION_1 + "/session") public class SessionController1_8 extends BaseRestController { - + @Autowired RestService restService; - + + private final Log log = LogFactory.getLog(getClass()); + /** * Tells the user their sessionId, and whether or not they are authenticated. - * + * * @param request * @return * @should return the session id if the user is authenticated @@ -54,20 +62,22 @@ public class SessionController1_8 extends BaseRestController { */ @RequestMapping(method = RequestMethod.GET) @ResponseBody - public Object get(WebRequest request) { + public Object get(WebRequest request, HttpServletRequest httpServletRequest) { boolean authenticated = Context.isAuthenticated(); SimpleObject session = new SimpleObject(); session.add("sessionId", request.getSessionId()).add("authenticated", authenticated); if (authenticated) { String repParam = request.getParameter(RestConstants.REQUEST_PROPERTY_FOR_REPRESENTATION); Representation rep = (repParam != null) ? restService.getRepresentation(repParam) : Representation.DEFAULT; - session.add("user", ConversionUtil.convertToRepresentation(Context.getAuthenticatedUser(), rep)); - session.add("locale", Context.getLocale()); + User user = Context.getAuthenticatedUser(); + session.add("locale", Context.getLocale()); session.add("allowedLocales", Context.getAdministrationService().getAllowedLocales()); + session.add("user", ConversionUtil.convertToRepresentation(user, rep)); + ChangePasswordController1_8.SessionListener.sessionCreated(user.getUuid(), httpServletRequest.getSession(false)); } return session; } - + @RequestMapping(method = RequestMethod.POST) @ResponseBody @ResponseStatus(value = HttpStatus.OK) @@ -87,10 +97,10 @@ public void post(@RequestBody Map body) { throw new APIException(" '" + localeStr + "' is not in the list of allowed locales."); } } - + /** * Logs the client out - * + * * @should log the client out */ @RequestMapping(method = RequestMethod.DELETE) @@ -99,5 +109,5 @@ public void post(@RequestBody Map body) { public void delete() { Context.logout(); } - + } diff --git a/omod-1.8/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8Test.java b/omod-1.8/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8Test.java index 85192ac98..70f8ff809 100644 --- a/omod-1.8/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8Test.java +++ b/omod-1.8/src/test/java/org/openmrs/module/webservices/rest/web/v1_0/controller/openmrs1_8/SessionController1_8Test.java @@ -30,24 +30,24 @@ import org.springframework.web.context.request.WebRequest; public class SessionController1_8Test extends BaseModuleWebContextSensitiveTest { - + private String SESSION_ID = "test-session-id"; - + private SessionController1_8 controller; - - private WebRequest request; - + + private ServletWebRequest request; + @Before public void before() { controller = new SessionController1_8(); MockHttpServletRequest hsr = new MockHttpServletRequest(); hsr.setSession(new MockHttpSession(new MockServletContext(), SESSION_ID)); request = new ServletWebRequest(hsr); - + Context.getAdministrationService().saveGlobalProperty( new GlobalProperty(OpenmrsConstants.GLOBAL_PROPERTY_LOCALE_ALLOWED_LIST, "en_GB, sp, fr")); } - + /** * @see SessionController1_8#delete() * @verifies log the client out @@ -58,15 +58,15 @@ public void delete_shouldLogTheClientOut() throws Exception { controller.delete(); Assert.assertFalse(Context.isAuthenticated()); } - + /** - * @see SessionController1_8#get(WebRequest) + * @see SessionController1_8#get(WebRequest, javax.servlet.http.HttpServletRequest) * @verifies return the session id if the user is authenticated */ @Test public void get_shouldReturnTheSessionIdAndUserIfTheUserIsAuthenticated() throws Exception { Assert.assertTrue(Context.isAuthenticated()); - Object ret = controller.get(request); + Object ret = controller.get(request, request.getRequest()); Object userProp = PropertyUtils.getProperty(ret, "user"); Assert.assertEquals(SESSION_ID, PropertyUtils.getProperty(ret, "sessionId")); Assert.assertEquals(true, PropertyUtils.getProperty(ret, "authenticated")); @@ -75,29 +75,29 @@ public void get_shouldReturnTheSessionIdAndUserIfTheUserIsAuthenticated() throws Assert.assertEquals(Context.getAuthenticatedUser().getPerson().getUuid(), PropertyUtils.getProperty(personProp, "uuid")); } - + @Test public void get_shouldReturnLocaleInfoIfTheUserIsAuthenticated() throws Exception { Assert.assertTrue(Context.isAuthenticated()); - Object ret = controller.get(request); + Object ret = controller.get(request, request.getRequest()); Assert.assertEquals(Context.getLocale(), PropertyUtils.getProperty(ret, "locale")); Assert.assertArrayEquals(Context.getAdministrationService().getAllowedLocales().toArray(), ((List) PropertyUtils.getProperty(ret, "allowedLocales")).toArray()); } - + /** - * @see SessionController1_8#get(WebRequest) + * @see SessionController1_8#get(WebRequest, javax.servlet.http.HttpServletRequest) * @verifies return the session id if the user is not authenticated */ @Test public void get_shouldReturnTheSessionIdIfTheUserIsNotAuthenticated() throws Exception { Context.logout(); Assert.assertFalse(Context.isAuthenticated()); - Object ret = controller.get(request); + Object ret = controller.get(request, request.getRequest()); Assert.assertEquals(SESSION_ID, PropertyUtils.getProperty(ret, "sessionId")); Assert.assertEquals(false, PropertyUtils.getProperty(ret, "authenticated")); } - + @Test public void post_shouldSetTheUserLocale() throws Exception { Locale newLocale = new Locale("sp"); @@ -106,14 +106,14 @@ public void post_shouldSetTheUserLocale() throws Exception { controller.post(new ObjectMapper().readValue(content, HashMap.class)); Assert.assertEquals(newLocale, Context.getLocale()); } - + @Test(expected = APIException.class) public void post_shouldFailWhenSettingIllegalLocale() throws Exception { String newLocale = "fOOb@r:"; String content = "{\"locale\":\"" + newLocale + "\"}"; controller.post(new ObjectMapper().readValue(content, HashMap.class)); } - + @Test(expected = APIException.class) public void post_shouldFailWhenSettingDisallowedLocale() throws Exception { String newLocale = "km_KH"; diff --git a/pom.xml b/pom.xml index 8061a66d1..36652541a 100644 --- a/pom.xml +++ b/pom.xml @@ -45,7 +45,7 @@ UTF-8 1.9.10 - 1.6 + 1.8 ${project.parent.artifactId} ${project.name} @@ -171,15 +171,15 @@ openmrs-web tests - + com.fasterxml.jackson.core jackson-databind 2.9.7 - - + + org.openmrs.test @@ -191,12 +191,12 @@ commons-codec commons-codec - + junit junit - + org.hamcrest hamcrest-core @@ -206,7 +206,7 @@ org.hamcrest hamcrest-library - + org.mockito mockito-core @@ -346,7 +346,7 @@ - org.eclipse.m2e @@ -507,7 +507,7 @@ omod-1.12 omod-2.0 omod-2.1 - omod-2.2 + omod-2.2 omod integration-tests