SPDX-License-Identifier: AGPL-3.0-or-later
Etherpad integration is centralized in lib/Service/EtherpadClient.php.
All Etherpad operations are executed through HTTP API calls with the configured API version.
For authenticated server-side Etherpad API calls, parameters (including API key) are sent in
application/x-www-form-urlencoded POST bodies instead of URL query strings.
Important:
- This app requires Etherpad API key mode (
authenticationMethod: "apikey"). - OAuth-only Etherpad configurations are not supported for this integration.
createPaddeletePadgetTextsetTextgetHTMLgetRevisionsCountgetReadOnlyIDcreateGroupcreateGroupPadcreateAuthorIfNotExistsForcreateSession
public- Direct pad ID (
nc-...). - No group session required.
- Direct pad ID (
protected- GroupPad ID (
g.<group>$<name>). - Access only with a valid Etherpad session (
sessionIDcookie).
- GroupPad ID (
Implemented in lib/Service/PadSessionService.php.
Normal protected open flow:
- Extract group ID from pad ID.
- Resolve Etherpad author context for the Nextcloud user.
- Create Etherpad session via
createSession. - Set
sessionIDcookie. - Open regular pad URL.
For normal authenticated users, the plugin now caches Etherpad author state per Nextcloud user in server-side user config:
- cached keys:
etherpad_author_idetherpad_author_display_name
- cache scope:
- per Nextcloud user
- not shared across users
- not persisted for public-share pseudo users (
public-share:*)
Open-path behavior:
- Try cached
authorIdfor the current Nextcloud user. - If the current display name differs from the cached synced name:
- call
setAuthorName - update cached name
- call
- Try
createSessionwith cachedauthorId. - If session creation fails with an Etherpad API error:
- clear cached author state
- retry full author bootstrap through
createAuthorIfNotExistsFor - then create session again
Why this exists:
- Without caching, a normal protected open typically required:
createAuthorIfNotExistsForsetAuthorNamecreateSession
- With cache hit and unchanged display name, the hot path is usually only:
createSession
This reduces Etherpad API round-trips on repeated opens without weakening access checks or moving trust to the client.
Cookie details:
- Name:
sessionID secure: truesamesite: Nonehttp_only: false(runtime compatibility with current Etherpad/socket flow on this deployment)- Domain handling:
- if
etherpad_cookie_domainis set, this value is used as-is - if empty, domain is derived from
etherpad_host- two-label host (for example
example.org) ->example.org - multi-label host (for example
pad.example.org) ->.example.org
- two-label host (for example
- derivation is skipped for IP hosts and invalid host values
- recommendation: use explicit
etherpad_cookie_domainin multi-subdomain/proxy setups
- if
Regression safety check:
tests/integration/e2e-protected-cookie-contract.shvalidates the protected open response cookie contract:- one
sessionIDSet-Cookieheader from app flow - includes
SecureandSameSite=None - excludes
HttpOnly(current runtime compatibility requirement)
- one
- Read-only URL is built via
getReadOnlyID. - Authenticated protected GroupPad opens still require a session.
- Public read-only shares of protected GroupPads do not create an Etherpad session.
- They render the last synced snapshot stored in the
.padfile. - If an HTML snapshot is stored, only a small tag whitelist is rendered (
p, lists, headings, basic inline formatting, block/code tags); attributes and dangerous tags are stripped. - If no HTML snapshot is stored, the viewer falls back to the text snapshot.
- This prevents the public share response from setting a session cookie that could also open the writable GroupPad URL.
- They render the last synced snapshot stored in the
PublicViewerController maps Nextcloud share permissions:
- protected share without update permission -> local
.padtext snapshot, no Etherpad cookie - public pad share without update permission -> Etherpad read-only URL
- share with update permission -> Etherpad editable
- API errors are propagated as
EtherpadClientException. - HTTP >= 400 and invalid JSON are treated as explicit failures.
- Critical lifecycle flows log failures and abort in a controlled way (no silent best effort).
- Protected open keeps author-cache fallback defensive:
- stale cached author IDs are cleared automatically when session creation fails
- author name sync failures do not block pad opening