Skip to content

Public Webforms: Authentication - #1791

Open
nospame wants to merge 4 commits into
ejp/public-webforms-basefrom
ejp/public-webforms-auth
Open

Public Webforms: Authentication#1791
nospame wants to merge 4 commits into
ejp/public-webforms-basefrom
ejp/public-webforms-auth

Conversation

@nospame

@nospame nospame commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Technical Summary

SAAS-19925, SAAS-19926, SAAS-19927

  • First of three PRs adding Public Web Apps Sessions (one-time links that let an
    unauthenticated recipient fill a single pre-designated form). This PR is the authentication layer
    only — recognizing and authenticating a public session, inbound and outbound.
    • CommCareSessionAuthFilter: a request with the CommCare-Public-Session: true header and a
      public_form_session_key cookie is wrapped as a PublicSessionCredential.
    • HqUserDetailsService: public sessions send the key as publicSessionKey (not sessionId) to
      session_details.
    • HqUserDetailsBean: new HMAC-authoritative public field; isAuthorized drops the
      synthetic-username check for public sessions but still requires the request domain to match.
    • PublicFormSessionAuth: authenticates formplayer's outbound HQ calls with the session key,
      selected in UserRestoreAspect#getHqAuth.
  • Design decision: the CommCare-Public-Session header is only a credential-routing hint
    trust comes solely from HQ's public field, returned over the HMAC-authenticated session_details
    call.

Code and PR description written or co-written by AI and edited by human. Review by commit.

Safety Assurance

Safety story

Purely additive and gated behind a new session type. The existing Django-cookie and HMAC auth paths
are untouched; the new branches only fire when the public header + cookie are present and HQ
confirms public. No public session can authenticate until HQ issues one-time links. No
existing data is read or written differently for non-public requests.

Automated test coverage

  • SessionAuthTests — credential routing, header-vs-cookie precedence, failure when the key is absent.
  • HqUserDetailsServiceTestspublicSessionKey wire format vs sessionId.
  • HqUserDetailsTests — domain-only isAuthorized for public sessions, public deserialization.
  • PublicFormSessionAuthTest, UserRestoreAspectTest — outbound-auth selection.

QA Plan

Public Webforms will get end-to-end QA before its release.

Special deploy instructions

Though public form sessions are not yet being created in HQ, this PR should not be deployed without changes from #1792, which add required security checks to public form sessions.

Rollback instructions

  • This PR can be reverted after deploy with no further considerations.

Review

  • The set of people pinged as reviewers is appropriate for the level of risk of the change.

nospame and others added 4 commits July 20, 2026 16:18
When a request carries the `CommCare-Public-Session: true` header and a
`public_form_session_key` cookie, the session auth filter now produces a
typed PublicSessionCredential instead of the Django sessionid string, and
HqUserDetailsService sends it to HQ's session_details endpoint as
`publicSessionKey` rather than `sessionId`.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
HQ's session_details response marks a public web apps session (one-time
link) with a JSON `public` field. Add a boolean publicSession field mapped
via @JsonProperty("public"). Uses a primitive boolean so a missing field
defaults to false, which matters because the bean is
@JsonIgnoreProperties(ignoreUnknown = true) and would otherwise silently
drop it.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
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) <[email protected]>
For a public web apps session, formplayer's outbound calls to HQ must send the
`public_form_session_key` cookie together with the `CommCare-Public-Session:
true` header, and must NOT send the Django `sessionid`.

- New `PublicFormSessionAuth` (an `HqAuth`) emits exactly that cookie+header
  pair and nothing else; its key is guarded and never logged.
- `UserRestoreAspect.getHqAuth` now selects the credential for the request:
  if the authenticated user is a public session it returns a
  `PublicFormSessionAuth` built from the session key, otherwise the existing
  `DjangoAuth`/null.

Gated on the HMAC-authenticated `public` field (`isPublicSession()`), never
on the client-supplied header; the public credential is preferred when both
signals are present.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.18919% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 70.31%. Comparing base (ef9096c) to head (5e70e2f).

Files with missing lines Patch % Lines
...are/formplayer/auth/CommCareSessionAuthFilter.java 80.00% 0 Missing and 2 partials ⚠️
...care/formplayer/services/HqUserDetailsService.java 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master    #1791      +/-   ##
============================================
+ Coverage     70.10%   70.31%   +0.21%     
- Complexity     2027     2046      +19     
============================================
  Files           257      259       +2     
  Lines          8004     8035      +31     
  Branches        762      767       +5     
============================================
+ Hits           5611     5650      +39     
+ Misses         2110     2100      -10     
- Partials        283      285       +2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@nospame
nospame changed the base branch from master to ejp/public-webforms-base July 28, 2026 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants