-
Notifications
You must be signed in to change notification settings - Fork 574
Fix HTTP session timestamp units #1621
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,7 @@ public class AwsHttpSession implements HttpSession { | |
| private int maxInactiveInterval; | ||
| private long lastAccessedTime; | ||
| private boolean valid; | ||
| private boolean isNew = true; | ||
|
|
||
| /** | ||
| * @param id A unique session identifier | ||
|
|
@@ -50,7 +51,7 @@ public AwsHttpSession(String id) { | |
| } | ||
| this.id = id; | ||
| attributes = new HashMap<>(); | ||
| creationTime = Instant.now().getEpochSecond(); | ||
| creationTime = Instant.now().toEpochMilli(); | ||
| maxInactiveInterval = SESSION_DURATION_SEC; | ||
| lastAccessedTime = creationTime; | ||
| valid = true; | ||
|
|
@@ -118,18 +119,27 @@ public void invalidate() { | |
|
|
||
| @Override | ||
| public boolean isNew() { | ||
| return lastAccessedTime == creationTime; | ||
| if (!valid) { | ||
| throw new IllegalStateException("Session is invalidated"); | ||
| } | ||
| return isNew; | ||
| } | ||
|
|
||
| private void touch() { | ||
| lastAccessedTime = Instant.now().getEpochSecond(); | ||
| lastAccessedTime = Instant.now().toEpochMilli(); | ||
| isNew = false; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [BUG] Setting
This is also an observable behavior change relative to the pre-PR code. Previously Since a new private void touch() {
lastAccessedTime = Instant.now().toEpochMilli();
}and keep |
||
| } | ||
|
|
||
| boolean isValid() { | ||
| if (lastAccessedTime - creationTime < maxInactiveInterval) { | ||
| return valid; | ||
| } else { | ||
| if (!valid) { | ||
| return false; | ||
| } | ||
|
|
||
| if (maxInactiveInterval <= 0) { | ||
| return true; | ||
| } | ||
|
|
||
| return Instant.now().toEpochMilli() - lastAccessedTime | ||
| < maxInactiveInterval * 1000L; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,8 +28,14 @@ void new_withValidId_setsIdCorrectly() { | |
|
|
||
| @Test | ||
| void new_creationTimePopulatedCorrectly() { | ||
| long beforeCreation = Instant.now().toEpochMilli(); | ||
|
|
||
| AwsHttpSession session = new AwsHttpSession("id"); | ||
| assertTrue(session.getCreationTime() > Instant.now().getEpochSecond() - 1); | ||
|
|
||
| long afterCreation = Instant.now().toEpochMilli(); | ||
|
|
||
| assertTrue(session.getCreationTime() >= beforeCreation); | ||
| assertTrue(session.getCreationTime() <= afterCreation); | ||
| assertEquals(AwsHttpSession.SESSION_DURATION_SEC, session.getMaxInactiveInterval()); | ||
| assertEquals(session.getLastAccessedTime(), session.getCreationTime()); | ||
| } | ||
|
|
@@ -64,16 +70,30 @@ void attributes_dataStoredCorrectly() throws InterruptedException { | |
| } | ||
|
|
||
| @Test | ||
| void validSession_expectCorrectValidationOrInvalidation() throws InterruptedException { | ||
| void validSession_expectCorrectValidationOrInvalidation() { | ||
| AwsHttpSession sess = new AwsHttpSession("id"); | ||
|
|
||
| assertTrue(sess.isValid()); | ||
| assertTrue(sess.isNew()); | ||
|
|
||
| Thread.sleep(1000); | ||
| sess.setAttribute("test", "test"); | ||
|
|
||
| assertFalse(sess.isNew()); | ||
|
|
||
| sess.invalidate(); | ||
|
|
||
| assertFalse(sess.isValid()); | ||
| assertNull(sess.getAttribute("test")); | ||
| assertThrows(IllegalStateException.class, sess::isNew); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [GENERAL] Replacing sess.invalidate();
assertFalse(sess.isValid());
assertNull(sess.getAttribute("test"));
assertThrows(IllegalStateException.class, sess::isNew); |
||
| } | ||
|
|
||
| @Test | ||
| void nonPositiveMaxInactiveIntervalDoesNotExpireSession() { | ||
| AwsHttpSession sess = new AwsHttpSession("id"); | ||
|
|
||
| sess.setMaxInactiveInterval(0); | ||
| assertTrue(sess.isValid()); | ||
|
|
||
| sess.setMaxInactiveInterval(-1); | ||
| assertTrue(sess.isValid()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[BUG] Throwing
IllegalStateExceptionfromisNew()matches the spec in isolation, but it is the only accessor on this class that does so, and the request object keeps handing out invalidated sessions.AwsHttpServletRequest.getSession(boolean)caches the instance and never clears it on invalidation:So after any framework code calls
session.invalidate()(a logout handler, for example), a laterrequest.getSession(false)still returns the dead session, and callingisNew()on it now throws an unchecked exception where it previously returned a boolean. MeanwhilegetAttribute,getCreationTimeandgetLastAccessedTimecontinue to answer normally on the same invalidated object, so a caller has no non-throwing way to detect the invalid state (isValid()is package-private).Two consistent options:
getSession(false)returnnull(or create a fresh session forgetSession(true)) once the cached session has been invalidated.isNew()total, leaving the invalidation contract out of this PR's scope.Note also that
isNew()throws only on explicitinvalidate(); a session that has exceededmaxInactiveIntervalhasvalid == trueand so does not throw, even thoughisValid()reportsfalse.