Skip to content

Commit

Permalink
Improve getTimeout() endpoint
Browse files Browse the repository at this point in the history
Use newer java.time.* API for generating serverTime and sessionExpiry.

Use Spring ResponseCookie API for producing serverTime and sessionExpiry
cookies.

Add more tests for the generated cookies.
  • Loading branch information
arteymix committed Aug 4, 2022
1 parent 45e5054 commit 274ba57
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 30 deletions.
56 changes: 29 additions & 27 deletions src/main/java/ubc/pavlab/rdp/controllers/MainController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@

import lombok.extern.apachecommons.CommonsLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import ubc.pavlab.rdp.services.UserService;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import javax.servlet.http.HttpSession;
import java.time.Duration;
import java.time.Instant;

@Controller
@CommonsLog
Expand All @@ -29,16 +30,34 @@ public String index() {
}

@GetMapping(value = { "/maintenance" })
public ModelAndView maintenance() {
return new ModelAndView( "error/maintenance" );
public String maintenance() {
return "error/maintenance";
}

@PreAuthorize("isAuthenticated()")
@GetMapping(value = "/gettimeout", produces = MediaType.TEXT_PLAIN_VALUE)
@ResponseBody
public String getTimeout( HttpServletRequest servletRequest, HttpServletResponse servletResponse ) {
addTimeoutCookies( servletRequest, servletResponse );
return "Session timeout refreshed.";
public ResponseEntity<String> getTimeout( HttpSession httpSession ) {
// Only set timeout cookie if the user is authenticated.
Instant currTime = Instant.now();
Duration timeoutInSeconds = Duration.ofSeconds( httpSession.getMaxInactiveInterval() ).minusSeconds( 60 ); // Subtracting by 60s to give an extra minute client-side.
Instant expiryTime = currTime.plus( timeoutInSeconds );

// Get cookie for server current time.
ResponseCookie serverTimeCookie = ResponseCookie.from( "serverTime", Long.toString( currTime.toEpochMilli() ) )
.path( "/" )
.build();

// Get cookie for expiration time (consistent with serverTime cookie).
ResponseCookie sessionExpiryCookie = ResponseCookie.from( "sessionExpiry", Long.toString( expiryTime.toEpochMilli() ) )
.path( "/" )
.build();

return ResponseEntity.ok()
.header( HttpHeaders.SET_COOKIE, serverTimeCookie.toString() )
.header( HttpHeaders.SET_COOKIE, sessionExpiryCookie.toString() )
.contentType( MediaType.TEXT_PLAIN )
.body( "Session timeout refreshed." );
}

@GetMapping(value = "/terms-of-service")
Expand All @@ -50,21 +69,4 @@ public String termsOfService() {
public String privacyPolicy() {
return "privacy-policy";
}

private void addTimeoutCookies( HttpServletRequest servletRequest, HttpServletResponse servletResponse ) {
// Only set timeout cookie if the user is authenticated.
long currTime = System.currentTimeMillis();
int TIMEOUT_IN_SECONDS = servletRequest.getSession().getMaxInactiveInterval() - 60; // Subtracting by 60s to give an extra minute client-side.
long expiryTime = currTime + TIMEOUT_IN_SECONDS * 1000;

// Get cookie for server current time.
Cookie serverTimeCookie = new Cookie( "serverTime", "" + currTime );
serverTimeCookie.setPath( "/" );
servletResponse.addCookie( serverTimeCookie );

// Get cookie for expiration time (consistent with serverTime cookie).
Cookie expiryCookie = new Cookie( "sessionExpiry", "" + expiryTime );
expiryCookie.setPath( "/" );
servletResponse.addCookie( expiryCookie );
}
}
24 changes: 21 additions & 3 deletions src/test/java/ubc/pavlab/rdp/controllers/MainControllerTest.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package ubc.pavlab.rdp.controllers;

import org.hamcrest.FeatureMatcher;
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -27,10 +29,10 @@

import java.util.EnumSet;

import static org.hamcrest.Matchers.closeTo;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static ubc.pavlab.rdp.util.TestUtils.createUser;

Expand Down Expand Up @@ -141,12 +143,28 @@ public void getHtmlStats_redirect3xx() throws Exception {
public void getTimeout_withUser_return200() throws Exception {
User user = createUser( 1 );
when( userService.findCurrentUser() ).thenReturn( user );
long timeoutInSeconds = 0L;
mvc.perform( get( "/gettimeout" ) )
.andExpect( status().isOk() )
.andExpect( content().contentTypeCompatibleWith( MediaType.TEXT_PLAIN ) )
.andExpect( content().string( "Session timeout refreshed." ) )
.andExpect( cookie().exists( "serverTime" ) )
.andExpect( cookie().exists( "sessionExpiry" ) );
.andExpect( cookie().value( "serverTime", asDouble( closeTo( System.currentTimeMillis(), 100 ) ) ) )
.andExpect( cookie().path( "serverTime", "/" ) )
.andExpect( cookie().secure( "serverTime", false ) )
.andExpect( cookie().httpOnly( "serverTime", false ) )
.andExpect( cookie().value( "sessionExpiry", asDouble( closeTo( System.currentTimeMillis() + 1000L * ( timeoutInSeconds - 60L ), 100 ) ) ) )
.andExpect( cookie().path( "sessionExpiry", "/" ) )
.andExpect( cookie().secure( "sessionExpiry", false ) )
.andExpect( cookie().httpOnly( "sessionExpiry", false ) );
}

private static FeatureMatcher<String, Double> asDouble( Matcher<Double> matcher ) {
return new FeatureMatcher<String, Double>( matcher, "", "" ) {
@Override
protected Double featureValueOf( String s ) {
return (double) Long.parseLong( s );
}
};
}

@Test
Expand Down

0 comments on commit 274ba57

Please sign in to comment.