Skip to content

Commit b13fb5e

Browse files
dkayiwaclaude
andauthored
Hold UpdateFilter authentication state per session (#6329)
UpdateFilter.authenticatedSuccessfully was an instance field on the singleton filter, so once any super user authenticated at the first step of the database-update wizard the flag stayed true for everyone. A concurrent unauthenticated request could then POST straight to the review-changes step and run the pending database updates on the strength of another user's login. Move the authenticated flag into the HttpSession so each session must authenticate for itself. The shared update-in-progress state is left global on purpose so multiple super users can still watch one running update. Updates the E2E helpers to session scope, makes resetRequest carry the session forward (a follow-up request is the same client), and adds a regression test that a second session cannot reuse another session's authentication. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent d992f17 commit b13fb5e

2 files changed

Lines changed: 41 additions & 19 deletions

File tree

web/src/main/java/org/openmrs/web/filter/update/UpdateFilter.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,13 @@ public class UpdateFilter extends StartupFilter {
8080

8181
private static final String PROGRESS_VM_AJAXREQUEST = "updateProgress.vm.ajaxRequest";
8282

83+
/**
84+
* Session attribute set once a super user authenticates at the first step and checked on every
85+
* later step. Holding it on the session, rather than on the shared filter instance, stops one
86+
* user's successful authentication from being reused by another user's unauthenticated request.
87+
*/
88+
private static final String AUTHENTICATED_SUCCESSFULLY = "updateFilter.authenticatedSuccessfully";
89+
8390
/**
8491
* The model object behind this set of screens
8592
*/
@@ -91,12 +98,6 @@ public class UpdateFilter extends StartupFilter {
9198
*/
9299
private static boolean updatesRequired = true;
93100

94-
/**
95-
* Used on all pages after the first to make sure the user isn't trying to cheat and do some url
96-
* magic to hack in.
97-
*/
98-
private volatile boolean authenticatedSuccessfully = false;
99-
100101
private UpdateFilterCompletion updateJob;
101102

102103
/**
@@ -157,8 +158,8 @@ protected synchronized void doPost(HttpServletRequest httpRequest, HttpServletRe
157158
log.debug("Attempting to authenticate user: " + username);
158159
if (authenticateAsSuperUser(username, password)) {
159160
log.debug("Authentication successful. Redirecting to 'reviewupdates' page.");
160-
// set a variable so we know that the user started here
161-
authenticatedSuccessfully = true;
161+
// mark this session as authenticated so later steps can verify it
162+
httpRequest.getSession().setAttribute(AUTHENTICATED_SUCCESSFULLY, Boolean.TRUE);
162163

163164
//Set variable to tell us whether updates are already in progress
164165
referenceMap.put("isDatabaseUpdateInProgress", isDatabaseUpdateInProgress);
@@ -211,7 +212,7 @@ protected synchronized void doPost(HttpServletRequest httpRequest, HttpServletRe
211212
// step two of wizard in case if there were some warnings
212213
else if (REVIEW_CHANGES.equals(page)) {
213214

214-
if (!authenticatedSuccessfully) {
215+
if (!Boolean.TRUE.equals(httpRequest.getSession().getAttribute(AUTHENTICATED_SUCCESSFULLY))) {
215216
// throw the user back to the main page because they are cheating
216217
renderTemplate(DEFAULT_PAGE, referenceMap, httpResponse);
217218
return;

web/src/test/java/org/openmrs/web/filter/update/UpdateFilterE2ETest.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ class UpdateFilterE2ETest {
4040

4141
private MockHttpServletResponse response;
4242

43+
/** Must match {@code UpdateFilter.AUTHENTICATED_SUCCESSFULLY}. */
44+
private static final String AUTHENTICATED_SUCCESSFULLY_ATTR = "updateFilter.authenticatedSuccessfully";
45+
4346
/**
4447
* Testable subclass that overrides renderTemplate and authenticateAsSuperUser to avoid Velocity
4548
* rendering and database access.
@@ -77,7 +80,6 @@ void setup() throws Exception {
7780
this.changes = null;
7881
}
7982
});
80-
setAuthenticatedSuccessfully(false);
8183
setDatabaseUpdateInProgress(false);
8284
UpdateFilter.setLockReleased(false);
8385

@@ -88,7 +90,6 @@ void setup() throws Exception {
8890
@AfterEach
8991
void cleanup() throws Exception {
9092
setDatabaseUpdateInProgress(false);
91-
setAuthenticatedSuccessfully(false);
9293
UpdateFilter.setUpdatesRequired(true);
9394
UpdateFilter.setLockReleased(false);
9495
}
@@ -184,6 +185,23 @@ void reviewChangesPage_shouldRedirectToMaintenanceIfNotAuthenticated() throws Ex
184185
assertEquals("maintenance.vm", filter.lastRenderedTemplate);
185186
}
186187

188+
@Test
189+
void reviewChangesPage_shouldNotReuseAnotherSessionsAuthentication() throws Exception {
190+
// one super user authenticates in their own session
191+
filter.authResult = true;
192+
MockHttpServletRequest authRequest = new MockHttpServletRequest();
193+
authRequest.setParameter("page", "maintenance.vm");
194+
authRequest.setParameter("username", "admin");
195+
authRequest.setParameter("password", "Admin123");
196+
filter.doPost(authRequest, new MockHttpServletResponse());
197+
198+
// a different, unauthenticated session must not inherit that authentication
199+
request.setParameter("page", "reviewchanges.vm");
200+
filter.doPost(request, response);
201+
202+
assertEquals("maintenance.vm", filter.lastRenderedTemplate);
203+
}
204+
187205
@Test
188206
void reviewChangesPage_shouldRenderReviewChangesWhenAuthenticated() throws Exception {
189207
setAuthenticatedSuccessfully(true);
@@ -431,20 +449,23 @@ private void clearErrors() throws Exception {
431449
}
432450

433451
private void resetRequest() {
452+
// carry the session forward so a follow-up request represents the same client (same session)
453+
MockHttpServletRequest previous = request;
434454
request = new MockHttpServletRequest();
455+
request.setSession(previous.getSession());
435456
response = new MockHttpServletResponse();
436457
}
437458

438-
private void setAuthenticatedSuccessfully(boolean value) throws Exception {
439-
Field field = UpdateFilter.class.getDeclaredField("authenticatedSuccessfully");
440-
field.setAccessible(true);
441-
field.setBoolean(filter, value);
459+
private void setAuthenticatedSuccessfully(boolean value) {
460+
if (value) {
461+
request.getSession().setAttribute(AUTHENTICATED_SUCCESSFULLY_ATTR, Boolean.TRUE);
462+
} else {
463+
request.getSession().removeAttribute(AUTHENTICATED_SUCCESSFULLY_ATTR);
464+
}
442465
}
443466

444-
private boolean getAuthenticatedSuccessfully() throws Exception {
445-
Field field = UpdateFilter.class.getDeclaredField("authenticatedSuccessfully");
446-
field.setAccessible(true);
447-
return field.getBoolean(filter);
467+
private boolean getAuthenticatedSuccessfully() {
468+
return Boolean.TRUE.equals(request.getSession().getAttribute(AUTHENTICATED_SUCCESSFULLY_ATTR));
448469
}
449470

450471
private void setDatabaseUpdateInProgress(boolean value) throws Exception {

0 commit comments

Comments
 (0)