Skip to content

fix(webdav): require credentials and apply the property permission result - #37179

Open
swicken wants to merge 2 commits into
mainfrom
issue-672-webdav-request-authorization
Open

fix(webdav): require credentials and apply the property permission result#37179
swicken wants to merge 2 commits into
mainfrom
issue-672-webdav-request-authorization

Conversation

@swicken

@swicken swicken commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

The WebDAV endpoints are served by a third-party library whose method handlers do not all
consult Resource.authorise(...). Two things follow from that, both measured against a running
instance.

A request carrying no Authorization header is handled by the handlers that run their own
sequence instead of the shared one. Anonymous PROPPATCH answered 207, anonymous OPTIONS
answered 200 on every /webdav/ mount point, and anonymous LOCK on a legacy language path
answered 500. WebDAV in dotCMS resolves every resource against a user, so there is nothing on
these endpoints to serve a caller who has not identified themselves. This adds a filter on
/webdav/* that answers those with the RFC 7235 challenge instead.

Separately, the permission check for PROPPATCH reports one problem per property named in the
request, so a body naming none reports an empty list of problems, which the caller reads as no
problem: the resource's answer is obtained and then discarded. WebdavPropertyAuthoriser reports
the refusal either way, and DotWebdavServlet installs it through the library's own extension
point, which reaches both handlers that check property permissions.

Behaviour

  • A request to /webdav/* with no Authorization header, or a blank one, gets 401 with
    WWW-Authenticate: Basic. Every method, whatever the body.
  • The status is set rather than raised as a container error, because web.xml maps 401 to
    /html/error/custom-error-page.jsp, which redirects, and a WebDAV client has nothing it can do
    with a 302 to a login page.
  • OPTIONS is not exempt. Being challenged on the first request is how a WebDAV client learns to
    send credentials at all.
  • Refusals go to the security log, alongside comparable events such as a back-end URL requested
    without a session.
  • Authenticated traffic is unchanged: PROPFIND with and without a body, OPTIONS, MKCOL,
    PUT, GET, PROPPATCH, LOCK and DELETE all behave as they did before.
  • A PROPPATCH whose body names no property is now refused for a caller holding no permission on
    the resource, instead of answering 207.

Merge order with #37167

Both branches add a <filter> and a <filter-mapping> immediately after NormalizationFilter,
so web.xml will conflict. #37167 should land first. When this branch is rebased, keep both
filters and map WebDavAuthenticationFilter before WebDavXmlValidationFilter: there is no
reason to buffer and parse a body for a caller who is about to be refused. The <servlet-class>
change here is in a part of the file #37167 does not touch and merges cleanly.

Two filters on the same path is deliberate. One asks whether there is a caller at all, by reading
a single header; the other asks whether the body is shaped the way RFC 4918 says, and has to
buffer and parse to answer. Merging them would put an XML parse behind a credentials check in one
class for no gain.

Testing

11 unit tests. WebDavAuthenticationFilterTest (6) covers the challenge and its header, every
WebDAV method, a blank Authorization header, non-HTTP pass-through, and pass-through for a
request that does carry credentials. WebdavPropertyAuthoriserTest (5) covers refusal when no
property was named, when the name set is null, one refusal per named property, and the allowed
case reporting nothing.

Against a running instance, before and after the change: anonymous PROPPATCH on three mount
points with three different no-property bodies went 207401, anonymous OPTIONS 200
401, anonymous LOCK on the legacy language path 500401, and nine functional checks
covering the traffic a real client sends are unchanged. Each guard was also verified by reverting
it and confirming the tests fail.

Not covered: no real desktop client was tested. Finder, Explorer and Office now meet a challenge
on OPTIONS where they previously got a 200. That is the standard handshake and every client
implements it, but it is reasoning rather than a measurement. There is also no CI coverage for
these methods — the WebDav Postman collection sends Basic auth on every request, so it
exercises the authenticated path only.

Closes dotCMS/private-issues#672

@swicken swicken added the Area : Backend PR changes Java/Maven backend code label Aug 24, 2026
@claude

claude Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @swicken's task in 3m 45s —— View job


Code Review — WebDAV credentials requirement + property permission fix

  • Read all 6 changed files
  • Verified SecurityLogger.logInfo(Class, Supplier) and UtilMethods.isSet(String) (trims → blank rejected)
  • Verified WebDAV auth model (Basic/Digest via Authorization header only — no session/cookie path)
  • Checked the PROPFIND blast-radius of installing a custom PropertyAuthoriser
  • Posted review

I reviewed both guards against the codebase, not just the diff. The design is sound and the two premises hold up:

  • Header-only credential check is correct. Every resource's authorise(...) returns false when auth == null, and auth is populated only from the Authorization header (authenticate(username, password)dotDavHelper.authorizePrincipal). There is no session/cookie auth path for /webdav/*, so requiring the header can't break an otherwise-valid authenticated client. Digest is also carried in the same header, so it's covered too.
  • setStatus over sendError is the right call given web.xml maps 401custom-error-page.jsp (which redirects). Good catch, and the test pins it.
  • Empty-fields PROPPATCH fix is real. Real resources (FolderResourceImpl, FileResourceImpl, HostResourceImpl, TemplateFileResourceImpl) return false for an authenticated-but-unauthorized user, so an empty refusal set genuinely reads as "allowed." Reporting a synthetic DAV:prop refusal closes it. Installing the authoriser for PROPFIND as well doesn't regress, since PROPFIND already runs through milton's shared authorise(...) gate — when that passes, checkPermissions sees authorise==true and returns null (no change).

New Issues

None blocking.

Notes (non-blocking)

  • 🟡 Low: WebDavAuthenticationFilter.java:74 — every anonymous /webdav/* request writes an INFO line to the security log (URI + remote addr). An unauthenticated flood is therefore an unbounded security-log write on a publicly reachable endpoint. This matches the existing dotCMS convention for auth failures (the "back-end URL without a session" case you cite logs the same way), so it isn't a new pattern — just worth being aware of if /webdav/* is internet-facing. No change required.
  • 🟡 Low: DotWebdavServlet.java:22 — accessing the inherited httpManager field directly and calling setPropertyPermissionService(...) couples this to milton 1.8.1.4 internals. Fine as-is (compiles + verified live per the PR), but a future milton bump is the thing that would break it silently. A one-line comment noting the version dependency, or a null-guard on httpManager, would make that failure mode obvious. Optional.

Test coverage

The 11 unit tests are focused and cover the meaningful branches (challenge + header, every method, blank/whitespace header, non-HTTP pass-through, credentialed pass-through; no-property / null-set / per-property / allowed / correct-delegation for the authoriser). DotWebdavServlet has no test, but it's thin servlet-init glue that's awkward to unit-test — acceptable, and it's exercised live per the PR notes.

Overall: clean, well-documented, and the reasoning in the class Javadocs matches what the code actually does. No changes required to merge (the merge-order note vs #37167 in the description still stands).
· issue-672-webdav-request-authorization

@swicken
swicken enabled auto-merge August 24, 2026 21:49
…rmission answer

The WebDAV endpoints are served by a third-party library whose method handlers do
not all consult Resource.authorise(). Two consequences, both measured against a
running instance:

A request carrying no Authorization header was handled by the handlers that run
their own sequence rather than the shared one. Anonymous PROPPATCH answered 207,
anonymous OPTIONS answered 200 on every /webdav/ mount, and anonymous LOCK on a
legacy language path answered 500. WebDavAuthenticationFilter answers those with
the RFC 7235 challenge instead, for every method and whatever the body. The
status is set rather than raised as a container error, because web.xml maps 401
to an error page that redirects and a WebDAV client cannot act on a 302.
Refusals go to the security log alongside comparable events.

PROPPATCH's permission check reported one problem per property named in the
request, so a body naming none reported an empty list of problems, which the
caller reads as no problem: the resource's answer was obtained and then
discarded. WebdavPropertyAuthoriser reports the refusal either way, and
DotWebdavServlet installs it, which reaches both handlers that check property
permissions.

Closes dotCMS/private-issues#672
The filter decides on one header, so the three bodies were three identical
runs of the test above it, and the name pointed the reader at the authoriser's
behaviour rather than the filter's. WebdavPropertyAuthoriserTest covers the
case a body naming no property actually exercises.
@swicken
swicken force-pushed the issue-672-webdav-request-authorization branch from 8d9545e to 4d8550d Compare August 25, 2026 14:20
@swicken
swicken added this pull request to the merge queue Aug 25, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants