Skip to content

Commit 0e9f605

Browse files
nospameclaude
andcommitted
Skip username authorization for public web apps sessions
When HqUserDetailsBean.publicSession is true, isAuthorized() no longer requires the request's username to equal the bean's username. Public web apps sessions authenticate via a single-use key that HQ validates server-to-server, and their username is a synthetic per-session string (not a real account), so echoing it is not a meaningful membership control. The requested domain is still required to be the session's domain (domains.contains(domain)). This keeps a session key from being replayed against a different domain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ec9ec73 commit 0e9f605

2 files changed

Lines changed: 33 additions & 0 deletions

File tree

src/main/java/org/commcare/formplayer/beans/auth/HqUserDetailsBean.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ public HqUserDetailsBean(String domain, String[] domains, String username, boole
5353
}
5454

5555
public boolean isAuthorized(String domain, String username) {
56+
if (publicSession) {
57+
// Public web apps sessions authenticate via a single-use key that HQ has already
58+
// validated and tied to exactly one domain. There is no real HQ account, so the
59+
// per-session username is not a meaningful check.
60+
return Arrays.asList(domains).contains(domain);
61+
}
5662
return isSuperUser || Arrays.asList(domains).contains(domain) && this.username.equals(
5763
username);
5864
}

src/test/java/org/commcare/formplayer/tests/HqUserDetailsTests.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,33 @@ public void testCommCareUserIsAuthorized() {
3939
Assertions.assertFalse(user.isAuthorized("domain", "wrong-bilbo"));
4040
}
4141

42+
@Test
43+
public void testPublicSessionIsAuthorized() {
44+
HqUserDetailsBean publicUser = new HqUserDetailsBean("domain",
45+
new String[]{"domain"}, "public_abc123@domain.commcarehq.org",
46+
false, new String[]{}, new String[]{});
47+
publicUser.setPublicSession(true);
48+
49+
// Synthetic username is not checked for a public session...
50+
Assertions.assertTrue(publicUser.isAuthorized("domain", "public_abc123@domain.commcarehq.org"));
51+
Assertions.assertTrue(publicUser.isAuthorized("domain", "some-other-name"));
52+
53+
// ...but the requested domain must still be the session's domain.
54+
Assertions.assertFalse(publicUser.isAuthorized("other-domain", "public_abc123@domain.commcarehq.org"));
55+
Assertions.assertFalse(publicUser.isAuthorized("other-domain", "some-other-name"));
56+
}
57+
58+
@Test
59+
public void testNonPublicSessionStillEnforcesUsername() {
60+
// Same shape as the public case but publicSession=false: the username check is enforced.
61+
HqUserDetailsBean regularUser = new HqUserDetailsBean("domain",
62+
new String[]{"domain"}, "real@domain.commcarehq.org",
63+
false, new String[]{}, new String[]{});
64+
65+
Assertions.assertTrue(regularUser.isAuthorized("domain", "real@domain.commcarehq.org"));
66+
Assertions.assertFalse(regularUser.isAuthorized("domain", "some-other-name"));
67+
}
68+
4269
@Test
4370
public void testPublicSessionDeserialization() throws Exception {
4471
ObjectMapper mapper = new ObjectMapper();

0 commit comments

Comments
 (0)