RFC 3744

Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol

πŸ“… published May 2004, from ietf

RFC 3744: Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol ✨

okay bestie, welcome to RFC 3744 πŸŽ€ This is part 1 of 13, so more is coming. Here's the title block, the stuff about who wrote it and when:

  • Network Working Group, RFC number 3744
  • Authors: G. Clemm (IBM), J. Reschke (greenbytes), E. Sedlar (Oracle Corporation), and J. Whitehead (U.C. Santa Cruz) πŸ‘‘
  • Category: Standards Track πŸ”₯
  • Date: May 2004 🌸

The header doesn't list anything this one obsoletes or updates, so there's no drama there 🫢


Status of This Memo πŸ’…

okay so this one is an Internet standards track protocol for the Internet community, and it's literally asking people to talk about it and suggest improvements πŸ¦‹ If you want to know where this protocol stands in standardization, check the current edition of the "Internet Official Protocol Standards" (STD 1). Distribution of this memo is unlimited, so share it freely πŸ’–


Copyright (C) The Internet Society (2004). All Rights Reserved.


Abstract 🧚

okay the big idea: this document defines a bunch of methods, headers, message bodies, properties, and reports that add Access Control extensions to the WebDAV Distributed Authoring Protocol ✨

The main thing it lets a client do is read and modify access control lists (ACLs). An ACL tells the server whether to allow or deny operations on a resource, like HyperText Transfer Protocol (HTTP) method invocations, for a given principal πŸ”

Principals get a lightweight representation as Web resources, which is lowkey genius because it means you can plug in tons of different user management repositories 🧠 Last bit: search operations let you find and manipulate principals using human names, so you don't need a random ID to look someone up πŸ™ˆ


Table of Contents πŸ“š

1.  Introduction . . . . . . . . . . . . . . . . . . . . . . . . .  4
    1.1.  Terms. . . . . . . . . . . . . . . . . . . . . . . . . .  6
    1.2.  Notational Conventions . . . . . . . . . . . . . . . . .  8
2.  Principals . . . . . . . . . . . . . . . . . . . . . . . . . .  8
3.  Privileges . . . . . . . . . . . . . . . . . . . . . . . . . .  8
    3.1.  DAV:read Privilege . . . . . . . . . . . . . . . . . . . 10
    3.2.  DAV:write Privilege. . . . . . . . . . . . . . . . . . . 10
    3.3.  DAV:write-properties Privilege . . . . . . . . . . . . . 10
    3.4.  DAV:write-content Privilege. . . . . . . . . . . . . . . 11
    3.5.  DAV:unlock Privilege . . . . . . . . . . . . . . . . . . 11
    3.6.  DAV:read-acl Privilege . . . . . . . . . . . . . . . . . 11
    3.7.  DAV:read-current-user-privilege-set Privilege. . . . . . 12
    3.8.  DAV:write-acl Privilege. . . . . . . . . . . . . . . . . 12
    3.9.  DAV:bind Privilege . . . . . . . . . . . . . . . . . . . 12
    3.10. DAV:unbind Privilege . . . . . . . . . . . . . . . . . . 12
    3.11. DAV:all Privilege. . . . . . . . . . . . . . . . . . . . 13
    3.12. Aggregation of Predefined Privileges . . . . . . . . . . 13
4.  Principal Properties . . . . . . . . . . . . . . . . . . . . . 13
    4.1.  DAV:alternate-URI-set. . . . . . . . . . . . . . . . . . 14
    4.2.  DAV:principal-URL. . . . . . . . . . . . . . . . . . . . 14
    4.3.  DAV:group-member-set . . . . . . . . . . . . . . . . . . 14
    4.4.  DAV:group-membership . . . . . . . . . . . . . . . . . . 14
5.  Access Control Properties. . . . . . . . . . . . . . . . . . . 15
    5.1.  DAV:owner. . . . . . . . . . . . . . . . . . . . . . . . 15
          5.1.1. Example: Retrieving DAV:owner . . . . . . . . . . 15
          5.1.2. Example: An Attempt to Set DAV:owner. . . . . . . 16
    5.2.  DAV:group. . . . . . . . . . . . . . . . . . . . . . . . 18
    5.3.  DAV:supported-privilege-set. . . . . . . . . . . . . . . 18
          5.3.1. Example: Retrieving a List of Privileges
                 Supported on a Resource . . . . . . . . . . . . . 19
    5.4.  DAV:current-user-privilege-set . . . . . . . . . . . . . 21
          5.4.1. Example: Retrieving the User's Current Set of
                 Assigned Privileges . . . . . . . . . . . . . . . 22
    5.5.  DAV:acl. . . . . . . . . . . . . . . . . . . . . . . . . 23
          5.5.1. ACE Principal . . . . . . . . . . . . . . . . . . 23
          5.5.2. ACE Grant and Deny. . . . . . . . . . . . . . . . 25
          5.5.3. ACE Protection. . . . . . . . . . . . . . . . . . 25
          5.5.4. ACE Inheritance . . . . . . . . . . . . . . . . . 25
          5.5.5. Example: Retrieving a Resource's Access Control
                 List. . . . . . . . . . . . . . . . . . . . . . . 25
    5.6.  DAV:acl-restrictions . . . . . . . . . . . . . . . . . . 27
          5.6.1. DAV:grant-only. . . . . . . . . . . . . . . . . . 27
          5.6.2. DAV:no-invert ACE Constraint. . . . . . . . . . . 28
          5.6.3. DAV:deny-before-grant . . . . . . . . . . . . . . 28
          5.6.4. Required Principals . . . . . . . . . . . . . . . 28
          5.6.5. Example: Retrieving DAV:acl-restrictions. . . . . 28

    5.7.  DAV:inherited-acl-set. . . . . . . . . . . . . . . . . . 29
    5.8.  DAV:principal-collection-set . . . . . . . . . . . . . . 30
          5.8.1. Example: Retrieving DAV:principal-collection-set. 30
    5.9.  Example: PROPFIND to retrieve access control properties. 32
6.  ACL Evaluation . . . . . . . . . . . . . . . . . . . . . . . . 36
7.  Access Control and existing methods. . . . . . . . . . . . . . 37
    7.1.  Any HTTP method. . . . . . . . . . . . . . . . . . . . . 37
          7.1.1. Error Handling. . . . . . . . . . . . . . . . . . 37
    7.2.  OPTIONS. . . . . . . . . . . . . . . . . . . . . . . . . 38
          7.2.1. Example - OPTIONS . . . . . . . . . . . . . . . . 39
    7.3.  MOVE . . . . . . . . . . . . . . . . . . . . . . . . . . 39
    7.4.  COPY . . . . . . . . . . . . . . . . . . . . . . . . . . 39
    7.5.  LOCK . . . . . . . . . . . . . . . . . . . . . . . . . . 39
8.  Access Control Methods . . . . . . . . . . . . . . . . . . . . 40
    8.1.  ACL. . . . . . . . . . . . . . . . . . . . . . . . . . . 40
          8.1.1. ACL Preconditions . . . . . . . . . . . . . . . . 40
          8.1.2. Example: the ACL method . . . . . . . . . . . . . 42
          8.1.3. Example: ACL method failure due to protected
                 ACE conflict. . . . . . . . . . . . . . . . . . . 43
          8.1.4. Example: ACL method failure due to an
                 inherited ACE conflict. . . . . . . . . . . . . . 44
          8.1.5. Example: ACL method failure due to an attempt
                 to set grant and deny in a single ACE . . . . . . 45
9.  Access Control Reports . . . . . . . . . . . . . . . . . . . . 46
    9.1.  REPORT Method. . . . . . . . . . . . . . . . . . . . . . 46
    9.2.  DAV:acl-principal-prop-set Report. . . . . . . . . . . . 47
          9.2.1. Example: DAV:acl-principal-prop-set Report. . . . 48
    9.3.  DAV:principal-match REPORT . . . . . . . . . . . . . . . 49
          9.3.1. Example: DAV:principal-match REPORT . . . . . . . 50
    9.4.  DAV:principal-property-search REPORT . . . . . . . . . . 51
          9.4.1. Matching. . . . . . . . . . . . . . . . . . . . . 53
          9.4.2. Example: successful DAV:principal-property-search
                 REPORT. . . . . . . . . . . . . . . . . . . . . . 54
    9.5.  DAV:principal-search-property-set REPORT . . . . . . . . 56
          9.5.1. Example: DAV:principal-search-property-set
                 REPORT. . . . . . . . . . . . . . . . . . . . . . 58
10. XML Processing . . . . . . . . . . . . . . . . . . . . . . . . 59
11. Internationalization Considerations. . . . . . . . . . . . . . 59
12. Security Considerations. . . . . . . . . . . . . . . . . . . . 60
    12.1. Increased Risk of Compromised Users. . . . . . . . . . . 60
    12.2. Risks of the DAV:read-acl and
          DAV:current-user-privilege-set Privileges. . . . . . . . 60
    12.3. No Foreknowledge of Initial ACL. . . . . . . . . . . . . 61
13. Authentication . . . . . . . . . . . . . . . . . . . . . . . . 61
14. IANA Considerations. . . . . . . . . . . . . . . . . . . . . . 62
15. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 62
16. References . . . . . . . . . . . . . . . . . . . . . . . . . . 62
    16.1. Normative References . . . . . . . . . . . . . . . . . . 62
    16.2. Informative References . . . . . . . . . . . . . . . . . 63
Appendices
A.  WebDAV XML Document Type Definition Addendum . . . . . . . . . 64
B.  WebDAV Method Privilege Table (Normative). . . . . . . . . . . 67
Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 71
Full Copyright Statement. . . . . . . . . . . . .  . . . . . . . . 72

Part 1 of 13 is done, bestie πŸ¦‹ Part 2 is next, and we're getting into the good stuff πŸ’œ

1. Introduction ✨

okay bestie, so the whole goal of the WebDAV access control extensions is to give everyone an interoperable way to handle discretionary access control πŸ” for content and metadata on WebDAV servers. it works on a repo as basic as a UNIX file system, but it also handles way fancier models πŸ’…. the core vibe is that who you are decides what you can do to a resource. the "who you are" part is a "principal" identifier, and users, client software, servers, and groups all get one πŸŽ€. the "what you can do" part lives in a single "access control list" (ACL) attached to each resource. an ACL holds a bunch of "access control entries" (ACEs), and each ACE names a principal and lists privileges that are either granted or denied to them. when a principal sends an operation (like an HTTP or WebDAV method) at a resource, the server checks the ACEs in the ACL to see if that principal is allowed πŸ™ˆ.

since every ACE has a principal's identifier in it, client software run by a human needs a way to pick that principal 🫢. this spec uses http(s) scheme URLs to identify principals, and those principals are WebDAV-capable resources. ngl, those URLs aren't guaranteed to make any sense to a human. like, http://www.example.com/u/256432 and http://www.example.com/people/Greg.Stein are both valid URLs that could point at the same principal πŸ’€. to fix that, every principal resource has the DAV:displayname property, which holds a human-readable name for it ✨.

okay now this is the tricky part 🧠. since one principal can have multiple URLs, it gets hard to tell which principal an ACE is actually talking about. a client can't figure out that an ACE granting read to http://www.example.com/people/Greg.Stein also affects http://www.example.com/u/256432. a client has no way to know that two URLs point at the same principal resource. so this spec says clients must use just one of the possible URLs for a principal when they make ACEs. wait, sorry, let me be precise, the original says "requires", not "must", so the real thing is: this specification requires clients to use just one of the many possible URLs for a principal when creating ACEs. a client can find out which URL to use by fetching the DAV:principal-URL property (Section 4.2) from a principal resource. and no matter which of the principal's URLs you send PROPFIND to, that property always gives back the same URL πŸ¦‹.

with hundreds to thousands of principals, the question becomes how a human picks just one 😭. one approach is to spread the principals across lots of collections so there are only a few per collection. for example, you could have a two level hierarchy where the first level has 36 collections (a-z, 0-9), and the second level has another 36, giving you collections /a/a/, /a/b/, ..., /a/z/. so a principal with the last name "Stein" would sit at /s/t/Stein. honestly that's pre-computing a common search on last name and baking it into a folder structure πŸ“. the catch is it only handles a small set of predefined searches, and walking down the hierarchy adds extra steps (down, then back up) even when the user already knows the name. organizing principal URLs into a hierarchy is a totally valid namespace choice, but users shouldn't be forced to navigate it just to pick a principal 🌸.

so this spec gives you substring searches over a small set of properties on principal resources ✨. you can search by last name, first name, user name, job title, and so on. two separate searches are supported, and both use the REPORT method: one to search principal resources (DAV:principal-property-search, Section 9.4), and one to find out which properties are searchable at all (DAV:principal-search-property-set, Section 9.5) πŸ”.

once a principal shows up in an ACE, the server evaluating that ACE has to know who is making the protocol request, and it has to check that they really are who they claim to be. that check is called authentication πŸ”‘. this spec intentionally skips authentication, since HTTP already has several authentication mechanisms [RFC2617]. still, some authentication mechanism has to be available to validate a principal's identity. HTTP Digest Authentication is one example, and every WebDAV compliant implementation is required to support it πŸ’œ.

these issues are out of scope for this doc, so don't come looking for them here πŸ™…β€β™€οΈ:

  • access control that only applies to one particular property on a resource (except the access control properties DAV:acl and DAV:current-user-privilege-set), instead of the whole resource,
  • role-based security (where a role is basically a dynamically defined group of principals),
  • how an ACL on a resource gets initialized,
  • an ACL that applies globally to all resources, rather than to one particular resource.
  • creating and maintaining resources that represent people or computational agents (principals), and groups of these.

here's how the spec is laid out πŸ—ΊοΈ. Section 1.1 defines the key concepts used all the way through, followed by a deeper look at principals (Section 2) and privileges (Section 3). properties defined on principals are in Section 4, and access control properties for content resources are in Section 5. how ACLs get evaluated is in Section 6. client discovery of access control capability using OPTIONS is in Section 7.2. interactions between access control and existing HTTP and WebDAV methods are in the rest of Section 7. the access control setting method, ACL, is in Section 8. four reports that give limited server-side searching are in Section 9. then there are sections on XML processing (Section 10), internationalization (Section 11), security considerations (Section 12), and authentication (Section 13) to round it out πŸŽ€. and there's an appendix (Appendix A) with an XML Document Type Definition (DTD) for the XML elements defined here 🌈.

1.1. Terms

this doc uses the terms defined in HTTP [RFC2616] and WebDAV [RFC2518]. on top of those, these terms are defined too πŸ“š:

principal

A "principal" is a distinct human or computational actor that initiates access to network resources. in this protocol, a principal is an HTTP resource that represents such an actor πŸ’–.

group

A "group" is a principal that represents a set of other principals πŸ«‚.

privilege

A "privilege" controls access to a particular set of HTTP operations on a resource πŸ”.

aggregate privilege

An "aggregate privilege" is a privilege that contains a set of other privileges πŸ“¦.

abstract privilege

The modifier "abstract", when applied to a privilege on a resource, means the privilege cannot be set in an access control element (ACE) on that resource 🚫.

access control list (ACL)

An "ACL" is a list of access control elements that define access control to a particular resource πŸ“.

access control element (ACE)

An "ACE" either grants or denies a particular set of (non-abstract) privileges for a particular principal βœ….

inherited ACE

An "inherited ACE" is an ACE that is dynamically shared from the ACL of another resource. when a shared ACE changes on the primary resource, it is also changed on inheriting resources πŸ¦‹.

protected property

A "protected property" is one whose value cannot be updated except by a method explicitly defined as updating that specific property. in particular, a protected property cannot be updated with a PROPPATCH request πŸ›‘οΈ.

1.2. Notational Conventions

the augmented BNF this doc uses to describe protocol elements is described in Section 2.1 of [RFC2616]. since that augmented BNF uses the basic production rules from Section 2.2 of [RFC2616], those rules apply here too 🧩.

the key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC2119] 🎯.

XML element definitions in this doc use XML element type declarations (as found in XML Document Type Declarations), described in Section 3.2 of [REC-XML]. when an XML element type in the "DAV:" namespace gets referenced in this doc outside of an XML fragment, the string "DAV:" gets prefixed to the element name πŸ’….

2. Principals

a principal is a network resource that represents a distinct human or computational actor that initiates access to network resources 🌸. users and groups are represented as principals in lots of implementations, and other types of principals are possible too. a URI of any scheme MAY be used to identify a principal resource 🌈. however, servers implementing this specification MUST expose principal resources at an http(s) URL. that's a privileged scheme that points to resources with extra properties, as described in Section 4 ✨. so a principal resource can have multiple URIs, and one of them has to be an http(s) scheme URL πŸ”—. although an implementation SHOULD support PROPFIND and MAY support PROPPATCH to access and modify information about a principal, it is not required to do so 🫢.

a principal resource may be a group, where a group is a principal that represents a set of other principals, called the members of the group πŸ‘―β€β™€οΈ. if a person or computational agent matches a principal resource that is a member of a group, they also match the group πŸ’œ. group membership is recursive, so if a principal is a member of group GRPA, and GRPA is a member of group GRPB, then the principal is also a member of GRPB πŸͺž.

3. Privileges πŸŽ€

Okay so privileges are basically the bouncers πŸ’…. Being able to run a method on a resource MUST be controlled by one or more privileges. If you're writing an extension that adds brand-new HTTP methods, you SHOULD say which privileges they need, either by defining new ones or by mapping to the ones below ✨. A principal with zero privileges on a resource MUST be denied any HTTP access to it, UNLESS they match an ACE built with the DAV:all, DAV:authenticated, or DAV:unauthenticated pseudo-principals (see Section 5.5.1) 🫢. Servers MUST report a 403 "Forbidden" error when access is denied, except when the privilege is about hiding whether the resource exists at all. In that case, 404 "Not Found" may be returned instead πŸ™ˆ.

Privileges can be containers for other privileges, and those are called "aggregate privileges" πŸ“¦. If a principal is granted or denied an aggregate privilege, it's semantically the same as granting or denying each privilege inside it, one by one. For example, an implementation might define add-member and remove-member privileges that control adding and removing members of a group πŸ‘―. Since those change the state of a group, they'd be aggregated under the DAV:write privilege on that group. So granting DAV:write on a group would also grant add-member and remove-member ✨.

Privileges can also be declared "abstract" for a given resource, which means they can't be set in an ACE on that resource πŸ¦‹. Aggregate and non-aggregate privileges can both be abstract. Abstract privileges are super useful for modeling privileges that wouldn't otherwise be exposed through the protocol. They also give server implementations flexibility in how they carry out the privileges defined in this spec πŸ’œ. Say a server can't separate the read-resource capability from the read-ACL capability. It can still model DAV:read and DAV:read-acl by declaring them abstract and putting them inside a non-abstract aggregate privilege (say, read-all) that holds both. Then you can set read-all to couple DAV:read and DAV:read-acl together, but you can't set DAV:read or DAV:read-acl on their own πŸ”. Since aggregates can be abstract too, abstract privileges can also be used to group and organize non-abstract ones πŸ—‚οΈ. Privilege containment loops are not allowed, so a privilege MUST NOT contain itself. For example, DAV:read cannot contain DAV:read πŸ™…β€β™€οΈ.

The privileges that apply to a resource can change based on its DAV:resourcetype, and they can also differ between server implementations 🌈. For interoperability, though, this spec defines a set of well-known privileges (like DAV:read, DAV:write, DAV:read-acl, DAV:write-acl, DAV:read-current-user-privilege-set, and DAV:all). These can at least be used to classify the other privileges on a given resource πŸ“. Access permissions on null resources (defined in [RFC2518], Section 3) are solely the ones they inherit, if any. They aren't discoverable either, meaning the access control properties from Section 5 aren't defined on null resources 🌸. When a null resource turns into a stateful one, its initial access control list is set by the server's default ACL value policy, if there is one ✨.

Server implementations MAY define new privileges beyond the ones in this spec 🧚. Privileges defined by individual implementations MUST NOT use the DAV: namespace. They should use a namespace they control instead, such as an http scheme URL πŸ”—.

3.1. DAV:read Privilege

The read privilege controls methods that return information about the state of a resource, including its properties πŸ‘€. The methods affected are GET and PROPFIND. Any implementation-defined privilege that also controls access to GET and PROPFIND must be aggregated under DAV:read. So if an ACL grants access to DAV:read, the client can expect that no other privilege needs to be granted to get into GET and PROPFIND πŸ’–. On top of that, the read privilege MUST control the OPTIONS method 🌟.

<!ELEMENT read EMPTY>

3.2. DAV:write Privilege

The write privilege controls methods that lock a resource or change its content, dead properties, or (for a collection) its membership. Examples are PUT and PROPPATCH ✍️. Heads up, though: state changes are also controlled through locking (see section 5.3 of [RFC2518]), so real write access means both the write privileges AND the write-locking requirements have to be met πŸ”’. Any implementation-defined privilege that also controls access to methods that modify content, dead properties, or collection membership must be aggregated under DAV:write. For example, if an ACL grants access to DAV:write, the client can expect that no other privilege needs to be granted to get PUT and PROPPATCH πŸ’….

<!ELEMENT write EMPTY>

3.3. DAV:write-properties Privilege

The DAV:write-properties privilege controls methods that modify a resource's dead properties, like PROPPATCH 🍬. Whether this privilege can be used to control access to any live properties is up to the implementation. Any implementation-defined privilege that also controls access to methods that modify dead properties must be aggregated under DAV:write-properties. For example, if an ACL grants access to DAV:write-properties, the client can safely expect that no other privilege needs to be granted to access PROPPATCH 🫧.

<!ELEMENT write-properties EMPTY>

3.4. DAV:write-content Privilege

The DAV:write-content privilege controls methods that modify the content of an existing resource, like PUT πŸ“. Any implementation-defined privilege that also controls access to content must be aggregated under DAV:write-content. For example, if an ACL grants access to DAV:write-content, the client can safely expect that no other privilege needs to be granted to access PUT. Fun twist: PUT aimed at an unmapped URI creates a brand-new resource, so it's actually controlled by the DAV:bind privilege on the parent collection instead 🏠.

<!ELEMENT write-content EMPTY>

3.5. DAV:unlock Privilege

The DAV:unlock privilege controls using the UNLOCK method when you're NOT the lock owner. (The principal who created a lock can always UNLOCK it, no question πŸ”‘.) The people who can lock a resource are usually the same people who can modify it, but servers can let certain kinds of admins unlock resources that other people locked 🦸. Any privilege that controls non-lock-owner access to UNLOCK MUST be aggregated under DAV:unlock.

A lock owner can always remove their lock by sending an UNLOCK with the right lock token and credentials. So even without DAV:unlock privilege, they can still remove locks they own πŸ™Œ. Anyone else can only remove a lock if they have DAV:unlock privilege AND they send an UNLOCK with the correct lock token. And lock timeout? Totally unaffected by DAV:unlock ⏰.

<!ELEMENT unlock EMPTY>

3.6. DAV:read-acl Privilege

The DAV:read-acl privilege controls using PROPFIND to retrieve the DAV:acl property of a resource πŸ•΅οΈβ€β™€οΈ.

<!ELEMENT read-acl EMPTY>

3.7. DAV:read-current-user-privilege-set Privilege

The DAV:read-current-user-privilege-set privilege controls using PROPFIND to retrieve the DAV:current-user-privilege-set property of a resource 🎯. Clients use this property to visually show which UI items depend on permissions. For example, they might gray out resources that aren't writable 🩢.

This privilege is kept separate from DAV:read-acl for a reason. Most users need access to the privileges available to the current user (since that's what builds the UI), but the full ACL can contain info that isn't appropriate for the current authenticated user 🀫. So the group of people who can view the full ACL is expected to be much smaller than the group who can read the current user privilege set. That's why each one needs its own privilege πŸ’œ.

<!ELEMENT read-current-user-privilege-set EMPTY>

3.8. DAV:write-acl Privilege

The DAV:write-acl privilege controls using the ACL method to modify a resource's DAV:acl property πŸ› οΈ.

<!ELEMENT write-acl EMPTY>

3.9. DAV:bind Privilege

The DAV:bind privilege lets a method add a new member URL to the specified collection, for example via PUT or MKCOL πŸͺ΄. It's ignored for resources that aren't collections.

<!ELEMENT bind EMPTY>

3.10. DAV:unbind Privilege

The DAV:unbind privilege lets a method remove a member URL from the specified collection, for example via DELETE or MOVE πŸ—‘οΈ. It's ignored for resources that aren't collections.

<!ELEMENT unbind EMPTY>

3.11. DAV:all Privilege

DAV:all is an aggregate privilege that contains the entire set of privileges that can be applied to the resource πŸ‘‘.

<!ELEMENT all EMPTY>

3.12. Aggregation of Predefined Privileges

Server implementations are free to aggregate the predefined privileges (defined above in Sections 3.1-3.10), subject to the following limitations 🧩:

  • DAV:read-acl MUST NOT contain DAV:read, DAV:write, DAV:write-acl, DAV:write-properties, DAV:write-content, or DAV:read-current-user-privilege-set.
  • DAV:write-acl MUST NOT contain DAV:write, DAV:read, DAV:read-acl, or DAV:read-current-user-privilege-set.
  • DAV:read-current-user-privilege-set MUST NOT contain DAV:write, DAV:read, DAV:read-acl, or DAV:write-acl.
  • DAV:write MUST NOT contain DAV:read, DAV:read-acl, or DAV:read-current-user-privilege-set.
  • DAV:read MUST NOT contain DAV:write, DAV:write-acl, DAV:write-properties, or DAV:write-content.
  • DAV:write MUST contain DAV:bind, DAV:unbind, DAV:write-properties, and DAV:write-content.

4. Principal Properties 🌷

Principals show up to clients as WebDAV resources, identified by a URL πŸͺͺ. A principal MUST have a non-empty DAV:displayname property (defined in Section 13.2 of [RFC2518]) and a DAV:resourcetype property (defined in Section 13.9 of [RFC2518]). On top of that, a principal MUST report the DAV:principal XML element in the value of its DAV:resourcetype property. The element type declaration for DAV:principal looks like this πŸ’•:

<!ELEMENT principal EMPTY>

This protocol defines some extra properties for a principal too. Getting access control info can be expensive for a server, so the name and value of these properties SHOULD NOT be returned by a PROPFIND allprop request (as defined in Section 12.14.1 of [RFC2518]) 🐒.

4.1. DAV:alternate-URI-set ✨

okay so this one is a protected property πŸ”’ and if it's not empty, it holds URIs pointing to network resources with extra info about the principal πŸ“‡. Think of it as a little "more about me" card πŸ’…. One expected use is storing an LDAP [RFC2255] scheme URL πŸ¦‹. A user-agent that spots an LDAP URL could use LDAP [RFC2251] to grab more machine-readable directory info about the principal and show it in its UI 🌸. Support for this property is REQUIRED πŸ’–, and the value is empty if no alternate URI exists for the principal 🫢.

<!ELEMENT alternate-URI-set (href*)>

4.2. DAV:principal-URL πŸŽ€

a principal can have many URLs 🌈, but there has to be one "principal URL" that clients can use to uniquely identify it πŸ”‘. This protected property holds the URL that MUST be used to identify this principal in an ACL request πŸ‘‘. Support for this property is REQUIRED ✨.

<!ELEMENT principal-URL (href)>

4.3. DAV:group-member-set 🧚

this property lives on a group principal and lists the principals that are direct members of that group πŸ’œ. A group can also be a member of another group 😭, so it can have indirect members too (aka the members of its direct members) πŸ“. A URL in the DAV:group-member-set for a principal MUST be the DAV:principal-URL of that principal πŸ”₯.

<!ELEMENT group-member-set (href*)>

4.4. DAV:group-membership πŸ’…

this protected property tells you which groups the principal is directly a member of πŸ¦‹. Note that a server may let a group be a member of another group 🫢. In that case, you'd need to query the DAV:group-membership of those other groups to figure out which groups the principal is indirectly in πŸ™ˆ. Support for this property is REQUIRED πŸ’–.

<!ELEMENT group-membership (href*)>

5. Access Control Properties πŸ”

this spec adds a bunch of new properties for WebDAV resources 🌸. Access control properties can be fetched like any other WebDAV property, using the PROPFIND method ✨. Getting access control info can be expensive on lots of servers πŸ’€, so a PROPFIND allprop request (as defined in Section 12.14.1 of [RFC2518]) SHOULD NOT return the names and values of the properties defined in this section πŸ™ˆ.

Access control properties (especially DAV:acl and DAV:inherited-acl-set) are defined on the resource identified by the Request-URI of a PROPFIND request 🧚. The big consequence? If a resource can be reached through multiple URIs, the access control property values are the same across all of those URIs 🌈.

HTTP resources that support the WebDAV Access Control Protocol MUST contain the following properties πŸ‘‘. Null resources (described in Section 3 of [RFC2518]) MUST NOT contain the following properties 🫢.

5.1. DAV:owner πŸ‘‘

this property identifies a particular principal as the "owner" of the resource πŸ’–. The owner often has special access control powers (e.g., the owner frequently has permanent DAV:write-acl privilege) πŸ”₯, so clients might show the resource owner in their user interface ✨.

Servers MAY implement DAV:owner as a protected property πŸ¦‹, and MAY return an empty DAV:owner element as the property value if no owner information is available 🌸.

<!ELEMENT owner (href?)>

5.1.1. Example: Retrieving DAV:owner πŸ“‡

this example shows a client request for the DAV:owner property from a collection resource at http://www.example.com/papers/ πŸŽ€. The principal making the request is authenticated with Digest authentication πŸ”‘. The DAV:owner value is the URL http://www.example.com/acl/users/gstein, wrapped in the DAV:href XML element πŸ’….

Request <<
PROPFIND /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="jim",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:owner/>
  </D:prop>
</D:propfind>
Response <<
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/papers/</D:href>
    <D:propstat>
      <D:prop>
        <D:owner>
          <D:href>http://www.example.com/acl/users/gstein</D:href>
        </D:owner>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

5.1.2. Example: An Attempt to Set DAV:owner πŸ™ˆ

this one shows a client trying to change DAV:owner on the resource at http://www.example.com/papers 😭. DAV:owner is protected on this server, so it replies with a 207 (Multi-Status) response containing a 403 (Forbidden) status code for the attempt to set DAV:owner πŸ’€. Section 8.2.1 of [RFC2518] covers PROPPATCH status code info, Section 11 of [RFC2518] covers the Multi-Status response, and Sections 1.6 and 3.12 of [RFC3253] cover extra error marshaling for PROPPATCH attempts on protected properties πŸ“š.

Request <<
PROPPATCH /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="jim",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propertyupdate xmlns:D="DAV:">
  <D:set>
    <D:prop>
      <D:owner>
        <D:href>http://www.example.com/acl/users/jim</D:href>
      </D:owner>
    </D:prop>
  </D:set>
</D:propertyupdate>
Response <<
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/papers/</D:href>
    <D:propstat>
      <D:prop><D:owner/></D:prop>
      <D:status>HTTP/1.1 403 Forbidden</D:status>
      <D:responsedescription>
        <D:error><D:cannot-modify-protected-property/></D:error>
        Failure to set protected property (DAV:owner)
      </D:responsedescription>
    </D:propstat>
  </D:response>
</D:multistatus>

5.2. DAV:group 🫢

this property identifies a particular principal as the "group" of the resource 🌈. You'll usually spot it on repositories that use the Unix privileges model 🧚.

Servers MAY implement DAV:group as a protected property ✨, and MAY return an empty DAV:group element as the property value if no group information is available 🌸.

<!ELEMENT group (href?)>

5.3. DAV:supported-privilege-set πŸ’–

this is a protected property that lists the privileges defined for the resource πŸ”’. Each privilege shows up as an XML element, and aggregate privileges list all of the privileges they aggregate as sub-elements πŸ“.

<!ELEMENT supported-privilege-set (supported-privilege*)>
<!ELEMENT supported-privilege
 (privilege, abstract?, description, supported-privilege*)>
<!ELEMENT privilege ANY>

An abstract privilege MUST NOT be used in an ACE for that resource πŸ™…. Servers MUST fail an attempt to set an abstract privilege πŸ’€.

<!ELEMENT abstract EMPTY>

A description is a human-readable explanation of what the privilege controls access to πŸ“–. Servers MUST indicate the human language of the description using the xml:lang attribute 🌍, and SHOULD consider the HTTP Accept-Language request header when picking one of several available languages 🫢.

<!ELEMENT description #PCDATA>

It's envisioned that a WebDAV ACL-aware admin client would list the supported privileges in a dialog box and let the user pick non-abstract privileges to apply in an ACE πŸ’…. The privileges tree is super useful for programmatically mapping well-known privileges (defined by WebDAV or other standards groups) onto the privileges a particular server supports πŸ¦‹. It also hides complexity: when an implementation allows a huge number of privileges, the tree shows aggregates to the user instead πŸ‘‘.

5.3.1. Example: Retrieving a List of Privileges Supported on a Resource

okay so this example is a client asking for the DAV:supported-privilege-set property on http://www.example.com/papers/ πŸ¦‹ the value comes back as a whole tree of supported privileges, and every privilege is named as "[XML Namespace , localname]" so nobody gets confused πŸ’…

[DAV:, all] (aggregate, abstract)
   |
   +-- [DAV:, read] (aggregate)
          |
          +-- [DAV:, read-acl] (abstract)
          +-- [DAV:, read-current-user-privilege-set] (abstract)
   |
   +-- [DAV:, write] (aggregate)
          |
          +-- [DAV:, write-acl] (abstract)
          +-- [DAV:, write-properties]
          +-- [DAV:, write-content]
   |
   +-- [DAV:, unlock]

this privilege tree is not normative 🌸 (except it has to reflect the normative aggregation rules from Section 3.12, so it's giving rules), and tons of other privilege trees could totally exist too 🌈

Request <<
PROPFIND /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="gclemm",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:supported-privilege-set/>
  </D:prop>
</D:propfind>
Response <<
HTTP/1.1 207 Multi-Status

Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/papers/</D:href>
    <D:propstat>
      <D:prop>
        <D:supported-privilege-set>
          <D:supported-privilege>
            <D:privilege><D:all/></D:privilege>
           <D:abstract/>
            <D:description xml:lang="en">
              Any operation
            </D:description>
            <D:supported-privilege>
              <D:privilege><D:read/></D:privilege>
              <D:description xml:lang="en">
                Read any object
              </D:description>
              <D:supported-privilege>
                <D:privilege><D:read-acl/></D:privilege>
                <D:abstract/>
                <D:description xml:lang="en">Read ACL</D:description>
              </D:supported-privilege>
              <D:supported-privilege>
                <D:privilege>
                  <D:read-current-user-privilege-set/>
                </D:privilege>
                <D:abstract/>
                <D:description xml:lang="en">
                  Read current user privilege set property
                </D:description>
              </D:supported-privilege>
            </D:supported-privilege>
            <D:supported-privilege>
              <D:privilege><D:write/></D:privilege>
              <D:description xml:lang="en">
                Write any object
              </D:description>
              <D:supported-privilege>
                <D:privilege><D:write-acl/></D:privilege>
                <D:description xml:lang="en">

                  Write ACL
                </D:description>
                <D:abstract/>
              </D:supported-privilege>
              <D:supported-privilege>
                <D:privilege><D:write-properties/></D:privilege>
                <D:description xml:lang="en">
                  Write properties
                </D:description>
              </D:supported-privilege>
              <D:supported-privilege>
                <D:privilege><D:write-content/></D:privilege>
                <D:description xml:lang="en">
                  Write resource content
                </D:description>
              </D:supported-privilege>
            </D:supported-privilege>
            <D:supported-privilege>
              <D:privilege><D:unlock/></D:privilege>
              <D:description xml:lang="en">
                Unlock resource
              </D:description>
            </D:supported-privilege>
          </D:supported-privilege>
        </D:supported-privilege-set>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

5.4. DAV:current-user-privilege-set

okay this one is so good 🫢 DAV:current-user-privilege-set is a protected property holding the exact set of privileges the server has computed for the currently authenticated HTTP user πŸ’– aggregate privileges and the privileges they contain are both listed. user-agents can look at this value and adjust their UI, like graying out a menu item or button for anything the current principal can't do πŸ™ˆ it's also super useful for figuring out what the current principal can do without actually trying the operation first ✨

<!ELEMENT current-user-privilege-set (privilege*)>
<!ELEMENT privilege ANY>

if the current user is granted a specific privilege, that privilege must belong to the set of privileges that may be set on this resource πŸŽ€ so, every element in the DAV:current-user-privilege-set property MUST identify a non-abstract privilege from the DAV:supported-privilege-set property. no exceptions bestie πŸ’…

5.4.1. Example: Retrieving the User's Current Set of Assigned Privileges

continuing from Section 5.3.1 🧚 this example has a client asking for the DAV:current-user-privilege-set property on the resource at http://www.example.com/papers/. the principal making the request has the username "khare", and the request uses Digest authentication πŸ”₯ "khare" has been granted the DAV:read privilege. DAV:read contains DAV:read-acl and DAV:read-current-user-privilege-set (see Section 5.3.1), so "khare" can read the ACL property and the DAV:current-user-privilege-set property too πŸ‘‘ but DAV:all, DAV:read-acl, DAV:write-acl and DAV:read-current-user-privilege-set are NOT listed in the value, since (for this example) they're abstract privileges πŸ’€ and DAV:write isn't listed either, because "khare" isn't in any ACE granting write permission to that principal πŸ™ˆ

Request <<
PROPFIND /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="khare",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:current-user-privilege-set/>
  </D:prop>
</D:propfind>
Response <<
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
  <D:href>http://www.example.com/papers/</D:href>
  <D:propstat>
    <D:prop>
      <D:current-user-privilege-set>
        <D:privilege><D:read/></D:privilege>
      </D:current-user-privilege-set>
    </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
  </D:propstat>
  </D:response>
</D:multistatus>

5.5. DAV:acl

this is a protected property that holds the list of access control entries (ACEs), which spell out which principals get which privileges on this resource 🌈

<!ELEMENT acl (ace*) >

each DAV:ace element says which privileges are either granted or denied to a single principal πŸ“ and if the DAV:acl property is empty, no principal gets any privilege at all πŸ™ˆ

<!ELEMENT ace ((principal | invert), (grant|deny), protected?,
               inherited?)>

5.5.1. ACE Principal

the DAV:principal element identifies the principal this ACE applies to πŸ’œ

<!ELEMENT principal (href | all | authenticated | unauthenticated
 | property | self)>

the current user matches DAV:href only if they're authenticated as being (or being a member of) the principal identified by the URL inside that DAV:href πŸ¦‹ the current user always matches DAV:all ✨

<!ELEMENT all EMPTY>

the current user matches DAV:authenticated only if they're authenticated 🫢

<!ELEMENT authenticated EMPTY>

the current user matches DAV:unauthenticated only if they're not authenticated 🌸

<!ELEMENT unauthenticated EMPTY>

okay here's the tea: DAV:all is the union of DAV:authenticated and DAV:unauthenticated πŸ”₯ for any given request, the user matches either DAV:authenticated or DAV:unauthenticated, but never both, because those two are disjoint sets πŸ’…

a DAV:property principal in a DAV:acl property of a resource only matches the current user if the value of the identified property on that resource contains at most one DAV:href XML element, the URI value of that DAV:href identifies a principal, and the current user is authenticated as being (or being a member of) that principal 🧚 for example, if the DAV:property element contained <DAV:owner/>, the current user would only match the DAV:property principal if they're authenticated as matching the principal identified by the DAV:owner property of the resource πŸ‘‘

<!ELEMENT property ANY>

the current user matches DAV:self in a DAV:acl property of the resource only if that resource is a principal and that principal matches the current user. or, if the principal is a group, a member of that group matches the current user πŸŽ€

<!ELEMENT self EMPTY>

some servers may support ACEs that apply to users NOT matching the current principal, like all users who aren't in a particular group πŸ™ˆ that can be done by wrapping the DAV:principal element with DAV:invert ✨

<!ELEMENT invert principal>

5.5.2. ACE Grant and Deny

each DAV:grant or DAV:deny element specifies the set of privileges to be either granted or denied to the specified principal πŸ’– a DAV:grant or DAV:deny element in the DAV:acl of a resource MUST only contain non-abstract elements from the DAV:supported-privilege-set of that resource. no abstract stuff sneaking in πŸ™…β€β™€οΈ

<!ELEMENT grant (privilege+)>
<!ELEMENT deny (privilege+)>
<!ELEMENT privilege ANY>

5.5.3. ACE Protection

a server shows that an ACE is protected by including the DAV:protected element in the ACE πŸ›‘οΈ if the ACL of a resource has an ACE with a DAV:protected element, trying to remove that ACE from the ACL MUST fail. protected means protected, periodt πŸ’…

<!ELEMENT protected EMPTY>

5.5.4. ACE Inheritance

if a DAV:inherited element is present, it means this ACE is inherited from another resource, which is identified by the URL inside a DAV:href element 🌈 an inherited ACE can't be changed directly. instead, you have to modify the ACL on the resource it's inherited from πŸ¦‹

ngl this part is a little tricky, so read carefully πŸ€“ ACE inheritance is NOT the same thing as ACL initialization. ACL initialization defines the ACL a newly created resource will use (if one isn't specified). ACE inheritance is about an ACE that's logically shared, so an update to the resource containing the ACE affects the ACE of every resource that inherits it ✨ how ACLs get initialized, or how ACEs get inherited, isn't defined by this document 🧚

<!ELEMENT inherited (href)>

5.5.5. Example: Retrieving a Resource's Access Control List πŸ¦‹

Okay so this one picks up right where the last examples left off (Sections 5.3.1 and 5.4.1) πŸ’…. The client asks for the DAV:acl property on http://www.example.com/papers/, and there are two ACEs in this ACL πŸŽ€

ACE #1: The group at http://www.example.com/acl/groups/maintainers (the site maintainers πŸ‘‘) gets DAV:write privilege. Since DAV:write contains the DAV:write-acl privilege (see Section 5.3.1) for this example, the "maintainers" group can also edit the access control list. Main character energy 😭

ACE #2: All principals (DAV:all) get DAV:read privilege. Since DAV:read contains DAV:read-acl and DAV:read-current-user-privilege-set for this example, every user (including all the "maintainers" members) can read DAV:acl and DAV:current-user-privilege-set 🌸

>> Request <<

PROPFIND /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="masinter",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:acl/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/papers/</D:href>
    <D:propstat>
      <D:prop>
        <D:acl>
        <D:ace>
          <D:principal>
            <D:href
            >http://www.example.com/acl/groups/maintainers</D:href>
          </D:principal>
          <D:grant>
            <D:privilege><D:write/></D:privilege>

          </D:grant>
        </D:ace>
        <D:ace>
          <D:principal>
            <D:all/>
          </D:principal>
          <D:grant>
            <D:privilege><D:read/></D:privilege>
          </D:grant>
        </D:ace>
      </D:acl>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

5.6. DAV:acl-restrictions πŸ”₯

This protected property tells clients which types of ACLs this server supports, so they don't go around getting errors all day πŸ™ˆ When a client tries to set an ACL through the ACL method, the server may reject that attempt if it doesn't match the spec. The following properties show the restrictions the client must observe before setting an ACL:

  • <grant-only> Deny ACEs are not supported
  • <no-invert> Inverted ACEs are not supported
  • <deny-before-grant> All deny ACEs must occur before any grant ACEs
  • <required-principal> Indicates which principals are required to be present
<!ELEMENT acl-restrictions (grant-only?, no-invert?,
                            deny-before-grant?,
                            required-principal?)>

5.6.1. DAV:grant-only ✨

This element says ACEs with deny clauses are not allowed. Nope, not happening πŸ™…β€β™€οΈ

<!ELEMENT grant-only EMPTY>

5.6.2. DAV:no-invert ACE Constraint πŸ’€

This element says ACEs with the <invert> element are not allowed.

<!ELEMENT no-invert EMPTY>

5.6.3. DAV:deny-before-grant 🧚

This element says all deny ACEs must come before all grant ACEs. Order matters, bestie πŸ“

<!ELEMENT deny-before-grant EMPTY>

5.6.4. Required Principals 🫢

The required principal elements list which principals must have an ACE defined in the ACL πŸ’œ

<!ELEMENT required-principal
  (all? | authenticated? | unauthenticated? | self? | href* |
   property*)>

For example, this element requires that the ACL contain a DAV:owner property ACE:

<D:required-principal xmlns:D="DAV:">
  <D:property><D:owner/></D:property>
</D:required-principal>

5.6.5. Example: Retrieving DAV:acl-restrictions 🌈

In this example, the client asks for the value of the DAV:acl-restrictions property. Digest authentication gives credentials for the principal operating the client πŸ”

>> Request <<

PROPFIND /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="srcarter",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:acl-restrictions/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/papers/</D:href>
    <D:propstat>
      <D:prop>
        <D:acl-restrictions>
          <D:grant-only/>
          <D:required-principal>
            <D:all/>
          </D:required-principal>
        </D:acl-restrictions>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

5.7. DAV:inherited-acl-set 🌸

This protected property holds a set of URLs pointing at other resources that also control access to this resource. To get a privilege on a resource, the ACL on that resource (in its DAV:acl property) has to grant it, AND the ACL of every resource listed in that resource's DAV:inherited-acl-set property has to grant it too. So basically, the privileges from the current ACL get ANDed with the privileges from each inherited ACL. Double check energy πŸ”₯

<!ELEMENT inherited-acl-set (href*)>

5.8. DAV:principal-collection-set πŸ‘‘

This protected property of a resource holds a set of URLs pointing at the root collections that contain the principals available on the server implementing this resource. A WebDAV Access Control Protocol user agent can use DAV:principal-collection-set to fetch the DAV:displayname property (specified in Section 13.2 of [RFC2518]) of all the principals on that server πŸ¦‹ That gives it human-readable names for each principal, which can show up in a user interface.

<!ELEMENT principal-collection-set (href*)>

Different servers can control different parts of the URL namespace, so different resources on the same host MAY have different DAV:principal-collection-set values 🌈 The collections in DAV:principal-collection-set MAY live on different hosts from the resource. The URLs in DAV:principal-collection-set SHOULD be http or https scheme URLs πŸ” For security and scalability reasons, a server MAY report only a subset of all the principal collections it knows about, so clients should not assume they got an exhaustive listing. A server MAY also choose to report none of the principal collections it knows about, in which case the property value is empty.

The value of DAV:principal-collection-set sets the scope of the DAV:principal-property-search REPORT (defined in Section 9.4). Clients use that REPORT to fill their user interface with a list of principals πŸŽ€ So if a server limits how much principal info a client can get, it'll mess with the client's ability to edit access control lists, because it's hard to find the URL of a principal to use in an ACE πŸ’€

5.8.1. Example: Retrieving DAV:principal-collection-set ✨

In this example, the client asks for the value of the DAV:principal-collection-set property on the collection at http://www.example.com/papers/. The property contains two URLs, http://www.example.com/acl/users/ and http://www.example.com/acl/groups/, both wrapped in DAV:href XML elements. Digest authentication gives credentials for the principal operating the client 🫢

The client could reasonably follow this up with two separate PROPFIND requests to get the DAV:displayname property of the members of the two collections (/acl/users and /acl/groups). That info could power a user interface for creating access control entries 🧚

>> Request <<

PROPFIND /papers/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="yarong",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:principal-collection-set/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/papers/</D:href>
    <D:propstat>
      <D:prop>
        <D:principal-collection-set>
          <D:href>http://www.example.com/acl/users/</D:href>
          <D:href>http://www.example.com/acl/groups/</D:href>
        </D:principal-collection-set>
      </D:prop>
    <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

5.9. Example: PROPFIND to retrieve access control properties πŸ’…

Okay bestie, this is the big access control showcase ✨ It shows how you can grab all the access control info with a PROPFIND request by asking for DAV:owner, DAV:supported-privilege-set, DAV:current-user-privilege-set, and DAV:acl πŸŽ€

>> Request <<

PROPFIND /top/container/ HTTP/1.1
Host: www.example.com
Content-type: text/xml; charset="utf-8"
Content-Length: xxx
Depth: 0
Authorization: Digest username="ejw",
  realm="users@example.com", nonce="...",
  uri="/top/container/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
  <D:prop>
    <D:owner/>
    <D:supported-privilege-set/>
    <D:current-user-privilege-set/>
    <D:acl/>
  </D:prop>
</D:propfind>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:"
               xmlns:A="http://www.example.com/acl/">
  <D:response>
    <D:href>http://www.example.com/top/container/</D:href>
    <D:propstat>
      <D:prop>
        <D:owner>
          <D:href>http://www.example.com/users/gclemm</D:href>
        </D:owner>
        <D:supported-privilege-set>
          <D:supported-privilege>
            <D:privilege><D:all/></D:privilege>
            <D:abstract/>
            <D:description xml:lang="en">
              Any operation
            </D:description>
            <D:supported-privilege>
              <D:privilege><D:read/></D:privilege>
              <D:description xml:lang="en">
                Read any object
              </D:description>
            </D:supported-privilege>
            <D:supported-privilege>
              <D:privilege><D:write/></D:privilege>
              <D:abstract/>
              <D:description xml:lang="en">
                Write any object
              </D:description>
              <D:supported-privilege>
                <D:privilege><A:create/></D:privilege>
                <D:description xml:lang="en">
                  Create an object
                </D:description>
              </D:supported-privilege>
              <D:supported-privilege>
                <D:privilege><A:update/></D:privilege>
                <D:description xml:lang="en">
                  Update an object
                </D:description>
              </D:supported-privilege>
            </D:supported-privilege>
            <D:supported-privilege>
              <D:privilege><A:delete/></D:privilege>
              <D:description xml:lang="en">
                Delete an object
              </D:description>
            </D:supported-privilege>
            <D:supported-privilege>
              <D:privilege><D:read-acl/></D:privilege>
              <D:description xml:lang="en">
                Read the ACL
              </D:description>
            </D:supported-privilege>
            <D:supported-privilege>
              <D:privilege><D:write-acl/></D:privilege>
              <D:description xml:lang="en">
                Write the ACL
              </D:description>
            </D:supported-privilege>
          </D:supported-privilege>
        </D:supported-privilege-set>
        <D:current-user-privilege-set>
          <D:privilege><D:read/></D:privilege>
          <D:privilege><D:read-acl/></D:privilege>
        </D:current-user-privilege-set>
        <D:acl>
          <D:ace>
            <D:principal>
              <D:href>http://www.example.com/users/esedlar</D:href>
            </D:principal>
            <D:grant>
              <D:privilege><D:read/></D:privilege>
              <D:privilege><D:write/></D:privilege>
              <D:privilege><D:read-acl/></D:privilege>
            </D:grant>
          </D:ace>
          <D:ace>
            <D:principal>
              <D:href>http://www.example.com/groups/mrktng</D:href>
            </D:principal>
            <D:deny>
              <D:privilege><D:read/></D:privilege>
            </D:deny>
          </D:ace>
          <D:ace>
            <D:principal>
              <D:property><D:owner/></D:property>
            </D:principal>
            <D:grant>
              <D:privilege><D:read-acl/></D:privilege>
              <D:privilege><D:write-acl/></D:privilege>
            </D:grant>
          </D:ace>
          <D:ace>
            <D:principal><D:all/></D:principal>
            <D:grant>
              <D:privilege><D:read/></D:privilege>
            </D:grant>
            <D:inherited>
              <D:href>http://www.example.com/top</D:href>
            </D:inherited>
          </D:ace>
        </D:acl>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

The value of the DAV:owner property is a single DAV:href XML element containing the URL of the principal that owns this resource πŸ‘‘

The value of the DAV:supported-privilege-set property is a tree of supported privileges (using "[XML Namespace , localname]" to identify each privilege) 🌳

[DAV:, all] (aggregate, abstract)
   |
   +-- [DAV:, read]
   +-- [DAV:, write] (aggregate, abstract)
          |
          +-- [http://www.example.com/acl, create]
          +-- [http://www.example.com/acl, update]
          +-- [http://www.example.com/acl, delete]
   +-- [DAV:, read-acl]
   +-- [DAV:, write-acl]

The DAV:current-user-privilege-set property contains two privileges, DAV:read, and DAV:read-acl. So the current authenticated user can only read the resource, and read the DAV:acl property on it πŸ™ˆ The DAV:acl property contains a set of four ACEs:

ACE #1: The principal identified by the URL http://www.example.com/users/esedlar is granted the DAV:read, DAV:write, and DAV:read-acl privileges. πŸ’–

ACE #2: The principals identified by the URL http://www.example.com/groups/mrktng are denied the DAV:read privilege. In this example, the principal URL identifies a group. πŸ“

ACE #3: In this ACE, the principal is a property principal, specifically the DAV:owner property. When evaluating this ACE, the value of the DAV:owner property is retrieved, and is examined to see if it contains a DAV:href XML element. If so, the URL within the DAV:href element is read, and identifies a principal. In this ACE, the owner is granted DAV:read-acl, and DAV:write-acl privileges. πŸ‘‘

ACE #4: This ACE grants the DAV:all principal (all users) the DAV:read privilege. This ACE is inherited from the resource http://www.example.com/top, the parent collection of this resource. πŸ¦‹


6. ACL Evaluation ✨

Okay so, WebDAV ACLs are evaluated in a similar way to ACLs on Windows NT and in NFSv4 [RFC3530]). An ACL gets evaluated to figure out whether or not access will be granted for a WebDAV request πŸ”₯ ACEs are kept in a particular order and get evaluated until all of the permissions the current request needs have been granted. At that point the ACL evaluation stops and access is granted πŸ’… But if, during ACL evaluation, a <deny> ACE (matching the current user) shows up for a privilege that hasn't been granted yet, the evaluation stops and access is denied πŸ™ˆ Not having all of the required privileges granted also means access is denied, periodt.

Note that the semantics of lots of other existing ACL systems can be represented with this mechanism, by mixing deny and grant ACEs 🌈 For example, think about the standard "rwx" privilege scheme used by UNIX. In this scheme, if the current user owns the file, access is granted if the matching privilege bit is set and denied if it isn't set, no matter what permissions are set on the file's group and for the world 🧚 An ACL for UNIX permissions of "r--rw-r--" might look something like this:

<D:acl>
  <D:ace>
    <D:principal>
      <D:property><D:owner/></D:property>
    </D:principal>
    <D:grant>
      <D:privilege><D:read/></D:privilege>
    </D:grant>
  </D:ace>
  <D:ace>
    <D:principal>
      <D:property><D:owner/></D:property>
    </D:principal>
    <D:deny>
      <D:privilege><D:all/></D:privilege>
    </D:deny>
  </D:ace>
  <D:ace>
    <D:principal>
      <D:property><D:group/></D:property>
    </D:principal>
    <D:grant>
      <D:privilege><D:read/></D:privilege>
      <D:privilege><D:write/></D:privilege>
    </D:grant>
  </D:ace>
  <D:ace>
    <D:principal>
      <D:property><D:group/></D:property>
    </D:principal>
    <D:deny>
      <D:privilege><D:all/></D:privilege>
    </D:deny>
  </D:ace>
  <D:ace>
    <D:principal><D:all></D:principal>
    <D:grant>
      <D:privilege><D:read/></D:privilege>
    </D:grant>
  </D:ace>
</D:acl>

and the <acl-restrictions> would be defined as:

<D:no-invert/>
<D:required-principal>
  <D:all/>
  <D:property><D:owner/></D:property>
  <D:property><D:group/><D:group/>
</D:required-principal>

Note that the client can still get errors from a UNIX server even if it's obeying the <acl-restrictions>. That includes <D:allowed-principal> (adding an ACE that specifies a principal other than the ones in the ACL above) or <D:ace-conflict> (trying to reorder the ACEs in the example above) πŸ₯Ί These particular implementation semantics are too complex to be captured with the simple (but general) declarative restrictions, so that's just how it goes ✨


7. Access Control and existing methods πŸ¦‹

Now we're getting to the fun part, bestie! This section defines how access control functionality affects the methods that already exist πŸŽ€

7.1. Any HTTP method 🌸

7.1.1. Error Handling πŸ’€

The WebDAV ACL mechanism requires the use of HTTP method "preconditions" as described in section 1.6 of RFC3253 for ALL HTTP methods. Every HTTP method also has an extra precondition called DAV:need-privileges. If an HTTP method fails because of insufficient privileges, the response body to the "403 Forbidden" error MUST contain the <DAV:error> element. That element contains the <DAV:need-privileges> element, which contains one or more <DAV:resource> elements. These say which resource didn't have enough privileges and what privileges were lacking 😭

<!ELEMENT need-privileges (resource)* >
<!ELEMENT resource ( href , privilege ) >

Since some methods need multiple permissions on multiple resources, this info is needed to clear up any ambiguity 🧩 There's no requirement that every privilege violation gets reported. For implementation reasons, some servers may only report the first privilege violation πŸ’… For example:

>> Request <<

MOVE /a/b/ HTTP/1.1
Host: www.example.com
Destination: http://www.example.com/c/d

>> Response <<

HTTP/1.1 403 Forbidden
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<D:error xmlns:D="DAV:">
  <D:need-privileges>
    <D:resource>
      <D:href>/a</D:href>
      <D:privilege><D:unbind/></D:privilege>
    </D:resource>
    <D:resource>
      <D:href>/c</D:href>
      <D:privilege><D:bind/></D:privilege>
    </D:resource>
  </D:need-privileges>
</D:error>

7.2. OPTIONS ✨

okay bestie, so if a server does access control, it MUST put access-control in the DAV response header of any OPTIONS response for any resource it hosts πŸ’…. And when that token shows up in the DAV header, it MUST mean the server covers every MUST level requirement and every REQUIRED feature in this spec. no half-baked support, it's all or nothing 🫢

7.2.1. Example - OPTIONS πŸŽ€

Request <<
OPTIONS /foo.html HTTP/1.1
Host: www.example.com
Content-Length: 0
Response <<
HTTP/1.1 200 OK
DAV: 1, 2, access-control
Allow: OPTIONS, GET, PUT, PROPFIND, PROPPATCH, ACL

here the response is basically saying "yep, I do access control πŸ”" and that /foo.html can have its access control list changed with the ACL method. iconic behaviour πŸ¦‹

7.3. MOVE 🚚

when a resource gets moved somewhere else with a MOVE request, the non-inherited and non-protected ACEs in its DAV:acl property MUST NOT be changed, or else the MOVE just fails πŸ’€. the inherited and protected ACEs? the spec is intentionally leaving those undefined 🌸 so server devs get to pick how they handle inheritance and protection. flexibility era ✨

7.4. COPY πŸͺž

the DAV:acl property on the destination resource of a COPY MUST end up the same as if that resource had been freshly created with an individual creation request, like MKCOL or PUT πŸ“. so if a client wants to keep the ACL through a copy, it has to read DAV:acl before the COPY, then do an ACL operation on the new resource to put the original list back, as much as that's possible. the copy doesn't carry it over automatically, so you gotta do the work yourself πŸ’…

7.5. LOCK πŸ”’

a lock on a resource makes sure only the lock owner can change ACEs that are neither inherited nor protected. those are the only ACEs a client can edit with an ACL request anyway 🧚 so a lock doesn't cover inherited or protected ACEs, because a client can't modify them with an ACL request on that resource in the first place. makes sense, no notes 🌈

8. Access Control Methods πŸ‘‘

8.1. ACL πŸŽ€

the ACL method is how you change the access control list of a resource, the one you can read through the DAV:acl property 🧁. it only lets you touch ACEs that are not inherited and not protected πŸ™ˆ. when you invoke it, every non-inherited, non-protected ACE in the list gets set to exactly match the ACEs inside the DAV:acl XML element (the one described in Section 5.5) of the request body. an ACL request body MUST contain only one DAV:acl XML element πŸ’œ. and unless those non-inherited and non-protected ACEs can be updated to be exactly what the ACL request asks for, the ACL request MUST fail πŸ’€. all or nothing, bestie 🫢

the ACEs you can see in DAV:acl might only be part of the full set on that resource. if so, an ACL request only changes the ACEs visible to the current user and leaves any invisible ACE alone πŸ¦‹

to avoid stepping on another client's DAV:acl changes, a client SHOULD grab a WebDAV lock on the resource before it reads the DAV:acl property of a resource it's planning to update πŸ”

Implementation Note: okay so two super common moves are adding an ACE to an existing list or removing one. to do that, a client uses PROPFIND to fetch the DAV:acl property, then parses the returned list and strips out all the inherited and protected ACEs (those are tagged with the DAV:inherited and DAV:protected XML elements). with what's left (the non-inherited, non-protected ACEs), the client can add or remove as many ACEs as it wants, then sends the final set in the request body of the ACL method. it's giving tidy housekeeping ✨

8.1.1. ACL Preconditions πŸ“‹

an implementation MUST enforce the constraints below on an ACL request πŸ’…. if one gets broken, the server MUST respond with a 403 (Forbidden) or a 409 (Conflict), and the indicated XML element MUST come back as a child of a top-level DAV:error element in an XML response body 🚨

these status elements are usually written as empty XML elements (they're defined as EMPTY in the DTD), but implementations MAY add extra descriptive XML elements as children of the status element 🌸. clients MUST be able to accept children of these status elements 🫢. clients that don't understand the extra XML elements should ignore them.

here's the full list of preconditions:

  • (DAV:no-ace-conflict): the ACEs submitted in the ACL request MUST NOT conflict with each other. this is the catchall error for when some implementation-specific ACL restriction got violated πŸ₯²
  • (DAV:no-protected-ace-conflict): the ACEs submitted in the ACL request MUST NOT conflict with the protected ACEs on the resource. for example, if the resource has a protected ACE granting DAV:write to a certain principal, it would be inconsistent for the ACL request to submit an ACE denying DAV:write to that same principal πŸ™…β€β™€οΈ
  • (DAV:no-inherited-ace-conflict): the ACEs submitted in the ACL request MUST NOT conflict with the inherited ACEs on the resource. for example, if the resource inherits an ACE from its parent collection granting DAV:write to a certain principal, it'd be inconsistent to submit an ACE denying DAV:write to that principal. reporting this error is up to the implementation, so implementations MUST either report it or allow the ACE to be set, and then let the normal ACE evaluation rules decide whether the new ACE actually changes what a specific principal can do 🧠
  • (DAV:limited-number-of-aces): the number of ACEs submitted in the ACL request MUST NOT go over the number of ACEs the resource allows. BUT ACL-compliant servers MUST support at least one ACE granting privileges to a single principal, and at least one ACE granting privileges to a group πŸ‘‘
  • (DAV:deny-before-grant): all non-inherited deny ACEs MUST come before all non-inherited grant ACEs πŸŽ€
  • (DAV:grant-only): the ACEs submitted in the ACL request MUST NOT include a deny ACE. this one only kicks in when the resource's ACL restrictions include the DAV:grant-only constraint (defined in Section 5.6.1) 🌷
  • (DAV:no-invert): the ACL request MUST NOT include a DAV:invert element. this only applies when the resource's ACL semantics include the DAV:no-invert constraint (defined in Section 5.6.2) πŸ”„
  • (DAV:no-abstract): the ACL request MUST NOT try to grant or deny an abstract privilege (see Section 5.3) 🚫
  • (DAV:not-supported-privilege): the ACEs submitted in the ACL request MUST be supported by the resource 🧩
  • (DAV:missing-required-principal): the result of the ACL request MUST have at least one ACE for each principal named in a DAV:required-principal XML element in the ACL semantics of that resource (see Section 5.5) 🫢
  • (DAV:recognized-principal): every principal URL in the ACL request MUST identify a principal resource πŸ”
  • (DAV:allowed-principal): the principals in the ACEs submitted in the ACL request MUST be allowed as principals for the resource. for instance, if a server only lets authenticated principals in, it wouldn't allow DAV:all or DAV:unauthenticated in an ACE, because those would open the door to unauthenticated access πŸšͺ

8.1.2. Example: the ACL method 🌈

in this example, user "fielding" (authenticated through the Authorization header) gives the principal at http://www.example.com/users/esedlar (aka the user "esedlar") read and write privileges πŸ’–. the owner of the resource gets read-acl and write-acl privileges, and everyone gets read privileges 🌍

Request <<
ACL /top/container/ HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Authorization: Digest username="fielding",
  realm="users@example.com", nonce="...",
  uri="/top/container/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:acl xmlns:D="DAV:">
  <D:ace>
    <D:principal>
      <D:href>http://www.example.com/users/esedlar</D:href>
    </D:principal>
    <D:grant>
      <D:privilege><D:read/></D:privilege>
      <D:privilege><D:write/></D:privilege>
    </D:grant>
  </D:ace>
  <D:ace>
    <D:principal>
      <D:property><D:owner/></D:property>
    </D:principal>
    <D:grant>
      <D:privilege><D:read-acl/></D:privilege>
      <D:privilege><D:write-acl/></D:privilege>
    </D:grant>
  </D:ace>
  <D:ace>
    <D:principal><D:all/></D:principal>
    <D:grant>
      <D:privilege><D:read/></D:privilege>
    </D:grant>
  </D:ace>
</D:acl>
Response <<
HTTP/1.1 200 OK

8.1.3. Example: ACL method failure due to protected ACE conflict πŸ’€

in this request, user "fielding" (again, authenticated through the Authorization header) tries to deny the principal at http://www.example.com/users/esedlar (the user "esedlar") write privileges πŸ™…β€β™€οΈ. before this request, the DAV:acl property had a protected ACE (see Section 5.5.3) granting DAV:owner the DAV:read and DAV:write privileges. and guess what? the principal at http://www.example.com/users/esedlar is the owner of the resource πŸ‘€. so the ACL method fails, because the new ACE clashes with the protected ACE and breaks the whole point of ACE protection πŸ₯²

Request <<
ACL /top/container/ HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Authorization: Digest username="fielding",
  realm="users@example.com", nonce="...",
  uri="/top/container/", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:acl xmlns:D="DAV:">
  <D:ace>
    <D:principal>

      <D:href>http://www.example.com/users/esedlar</D:href>
    </D:principal>
    <D:deny>
      <D:privilege><D:write/></D:privilege>
    </D:deny>
  </D:ace>
</D:acl>
Response <<
HTTP/1.1 403 Forbidden
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:error xmlns:D="DAV:">
  <D:no-protected-ace-conflict/>
</D:error>

8.1.4. Example: ACL method failure due to an inherited ACE conflict ✨

okay so in this request, user "ejw" (authenticated by the Authorization header) tries to change the ACL on http://www.example.com/top/index.html πŸ’… and this resource already has two inherited ACEs πŸ‘€

Inherited ACE #1 grants the principal at http://www.example.com/users/ejw (aka "ejw") the http://www.example.com/privs/write-all privilege and DAV:read-acl. on this server, http://www.example.com/privs/write-all is an aggregate privilege that contains DAV:write and DAV:write-acl πŸŽ€

Inherited ACE #2 grants DAV:all the DAV:read privilege 🌸

the request tries to set a (non-inherited) ACE that denies that same ejw principal DAV:write permission. that clashes with inherited ACE #1, so it's a conflict πŸ™ˆ ngl, whether the server reports an inherited ACE conflict is up to that server's implementation. another server could just let the new ACE in, then use the normal ACE evaluation rules to figure out whether it actually changes what ejw can do ✨

>> Request <<

ACL /top/index.html HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Authorization: Digest username="ejw",
  realm="users@example.com", nonce="...",
  uri="/top/index.html", response="...", opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:acl xmlns:D="DAV:" xmlns:F="http://www.example.com/privs/">
  <D:ace>
    <D:principal>
       <D:href>http://www.example.com/users/ejw</D:href>
    </D:principal>
    <D:grant><D:write/></D:grant>
  </D:ace>
</D:acl>

>> Response <<

HTTP/1.1 403 Forbidden
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:error xmlns:D="DAV:">
  <D:no-inherited-ace-conflict/>
</D:error>

8.1.5. Example: ACL method failure due to an attempt to set grant and deny in a single ACE πŸ“

in this one, user "ygoland" (authenticated by the Authorization header) tries to change the ACL on http://www.example.com/diamond/engagement-ring.gif πŸ’ the ACL request has a single ACE that's both syntactically and semantically wrong 😭 it tries to grant the group at http://www.example.com/users/friends DAV:read and deny the principal at http://www.example.com/users/ygoland-so (aka "ygoland-so") DAV:read too. but you can't have multiple principal elements or both a grant and a deny element in the same ACE. so the request fails on bad syntax πŸ’€

>> Request <<

ACL /diamond/engagement-ring.gif HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Authorization: Digest username="ygoland",
  realm="users@example.com", nonce="...",
  uri="/diamond/engagement-ring.gif", response="...",
  opaque="..."

<?xml version="1.0" encoding="utf-8" ?>
<D:acl xmlns:D="DAV:">
  <D:ace>
    <D:principal>
      <D:href>http://www.example.com/users/friends</D:href>
    </D:principal>
    <D:grant><D:read/></D:grant>
    <D:principal>
      <D:href>http://www.example.com/users/ygoland-so</D:href>
    </D:principal>
    <D:deny><D:read/></D:deny>
  </D:ace>
</D:acl>

>> Response <<

HTTP/1.1 400 Bad Request
Content-Length: 0

Note that if the request had been split into two ACEs, one to grant and one to deny, it would have been syntactically well formed 🫢


9. Access Control Reports πŸ“Š

9.1. REPORT Method πŸ”

okay the REPORT method (defined in Section 3.6 of [RFC3253]) is basically an extensible way to get information about a resource ✨ PROPFIND gives you the value of one or more named properties, but REPORT can do way more complex processing. it's super useful when the server has everything it needs to handle a complex request (like a query) and the client would otherwise need a bunch of separate requests to get the same info 🧚

A server that supports the WebDAV Access Control Protocol MUST support the DAV:expand-property report (defined in Section 3.8 of [RFC3253]) πŸ’œ

9.2. DAV:acl-principal-prop-set Report πŸŽ€

the DAV:acl-principal-prop-set report returns, for every principal in the DAV:acl property of the Request-URI that's identified by an http(s) URL or by a DAV:property principal, the values of the properties listed in the REPORT request body πŸ’– if a principal URL shows up more than once, the report MUST return that principal's properties only once. support for this report is REQUIRED 🌈

one big use for this is grabbing the human readable name (from the DAV:displayname property) of each principal in an ACL. that's great for building UIs that show each ACE in a way people can actually read 😍

Marshalling

the request body MUST be a DAV:acl-principal-prop-set XML element.

<!ELEMENT acl-principal-prop-set ANY>
ANY value: a sequence of one or more elements, with at most one
           DAV:prop element.
prop: see RFC 2518, Section 12.11

this report is only defined when the Depth header has the value "0". other values get you a 400 (Bad Request) error response. Note that [RFC3253], Section 3.6, says that if the Depth header isn't there, it defaults to "0" πŸ™ˆ

the response body for a successful request MUST be a DAV:multistatus XML element (basically the same format as a PROPFIND response). if there are no response elements, the returned multistatus XML element is empty.

multistatus: see RFC 2518, Section 12.9

the response body for a successful DAV:acl-principal-prop-set REPORT request MUST contain a DAV:response element for each principal identified by an http(s) URL that's listed in a DAV:principal XML element of an ACE within the DAV:acl property of the resource identified by the Request-URI 🫢

Postconditions:

  • (DAV:number-of-matches-within-limits): the number of matching principals must fall within server-specific, predefined limits. for example, this could be triggered if a search would return an extremely large number of responses 😭

9.2.1. Example: DAV:acl-principal-prop-set Report πŸ¦‹

Resource http://www.example.com/index.html has an ACL with three ACEs:

ACE #1: all principals (DAV:all) have DAV:read and DAV:read-current-user-privilege-set access πŸ‘‘

ACE #2: the principal at http://www.example.com/people/gstein (the user "gstein") is granted DAV:write, DAV:write-acl, and DAV:read-acl privileges ✨

ACE #3: the group at http://www.example.com/groups/authors (the "authors" group) is granted DAV:write and DAV:read-acl privileges 🌸

this example shows a DAV:acl-principal-prop-set report asking for the DAV:displayname property. it returns the DAV:displayname for http://www.example.com/people/gstein and http://www.example.com/groups/authors, but not for DAV:all, since that isn't an http(s) URL πŸ’…

>> Request <<

REPORT /index.html HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Depth: 0

<?xml version="1.0" encoding="utf-8" ?>
<D:acl-principal-prop-set xmlns:D="DAV:">
  <D:prop>
    <D:displayname/>
  </D:prop>
</D:acl-principal-prop-set>

>> Response <<

HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/people/gstein</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>Greg Stein</D:displayname>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>http://www.example.com/groups/authors</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>Site authors</D:displayname>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

9.3. DAV:principal-match REPORT πŸͺž

the DAV:principal-match REPORT is used to find all members (at any depth) of the collection identified by the Request-URI that are principals and that match the current user πŸ’– if the collection has principals in it, the report can pick out every member that matches the current user. or, if the collection has resources with a property that identifies a principal (like DAV:owner), the report can find every member whose property identifies a principal that matches the current user. for example, this report can return all the resources in a collection hierarchy that are owned by the current user 🧚 support for this report is REQUIRED ✨

Marshalling:

the request body MUST be a DAV:principal-match XML element.

<!ELEMENT principal-match ((principal-property | self), prop?)>
<!ELEMENT principal-property ANY>
ANY value: an element whose value identifies a property.  The
expectation is the value of the named property typically contains
an href element that contains the URI of a principal
<!ELEMENT self EMPTY>
prop: see RFC 2518, Section 12.11

this report is only defined when the Depth header has the value "0". other values get a 400 (Bad Request) error response. Note that [RFC3253], Section 3.6, states that if the Depth header is not present, it defaults to a value of "0". the response body for a successful request MUST be a DAV:multistatus XML element. if there are no response elements, the returned multistatus XML element is empty.

multistatus: see RFC 2518, Section 12.9

the response body for a successful DAV:principal-match REPORT request MUST contain a DAV:response element for each member of the collection that matches the current user 🌈 when the DAV:principal-property element is used, a match happens if the current user is matched by the principal identified by the URI in the DAV:href element of the property named by DAV:principal-property. when the DAV:self element is used in a DAV:principal-match report issued against a group, it matches the group if a member identifies the same principal as the current user πŸ‘‘

if DAV:prop is specified in the request body, the properties in that DAV:prop element MUST be reported in the DAV:response elements πŸŽ€

9.3.1. Example: DAV:principal-match REPORT πŸŽ€

okay so this example finds the members of the collection at http://www.example.com/doc that are owned by the current user. 🫢 The current user is "gclemm", and she logs in with Digest authentication πŸ”

REPORT /doc/ HTTP/1.1
Host: www.example.com
Authorization: Digest username="gclemm",
  realm="users@example.com", nonce="...",
  uri="/papers/", response="...", opaque="..."
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx
Depth: 0
<?xml version="1.0" encoding="utf-8" ?>
<D:principal-match xmlns:D="DAV:">
  <D:principal-property>
    <D:owner/>
  </D:principal-property>
</D:principal-match>
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset="utf-8"
Content-Length: xxxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:">
  <D:response>
    <D:href>http://www.example.com/doc/foo.html</D:href>
    <D:status>HTTP/1.1 200 OK</D:status>
  </D:response>
  <D:response>
    <D:href>http://www.example.com/doc/img/bar.gif</D:href>
    <D:status>HTTP/1.1 200 OK</D:status>
  </D:response>
</D:multistatus>

9.4. DAV:principal-property-search REPORT ✨

the DAV:principal-property-search REPORT searches for every principal whose properties contain character data that matches the search criteria in the request. πŸ’… A big use case is finding a person's or group's principal URL by searching for them by name. That works by searching DAV:displayname, which every principal has.

the server gets to decide how matching works: exact, substring, or prefix matching, and whether it's case-sensitive. 🌸 This is on purpose, so the report can work with lots of different user management systems. If the server's user store doesn't force a particular behaviour, the preferred default is caseless substring matching.

for efficiency, servers usually don't support searching on every property. πŸ™ˆ If a search asks for a property that isn't searchable on a given principal, that principal won't match.

Support for the DAV:principal-property-search report is REQUIRED. πŸ”₯

Implementation Note: The value of a WebDAV property is a sequence of well-formed XML, so it can include any character in the Unicode/ISO-10646 standard, which means most known characters in human languages. 🌍 Case mapping differs a lot between languages, so case-insensitive matching is tricky. Servers that do substring matching are strongly encouraged to look at "The Unicode Standard" [UNICODE4], especially Section 5.18, Subsection "Caseless Matching", for guidance on their case-insensitive matching algorithms. πŸ“š
Implementation Note: Some implementations of this protocol store principal metadata in an LDAP repository. The schema for each attribute (kind of like a WebDAV property) in an LDAP repository says whether it supports case-sensitive or caseless searching. 🧚 Leaving the search method up to the server means the default LDAP attribute search behaviour can be used when implementing the DAV:principal-property-search report, which is a huge perk.

Marshalling:

The request body MUST be a DAV:principal-property-search XML element containing a search specification and an optional list of properties. ✨ For every principal that matches the search specification, the response will contain the value of the requested properties on that principal.

<!ELEMENT principal-property-search
 ((property-search+), prop?, apply-to-principal-collection-set?) >

By default, the report searches all members (at any depth) of the collection identified by the Request-URI. πŸ’– If DAV:apply-to-principal-collection-set is specified in the request body, the request is applied to each collection identified by the DAV:principal-collection-set property of the resource identified by the Request-URI instead.

The DAV:property-search element holds a prop element that lists the properties to search, plus a match element that holds the search string.

<!ELEMENT property-search (prop, match) >
prop: see RFC 2518, Section 12.11

<!ELEMENT match #PCDATA >

Multiple property-search elements, or multiple elements inside a DAV:prop element, are interpreted with a logical AND. 🧠

This report is only defined when the Depth header has the value "0". Other values get a 400 (Bad Request) error response. πŸ™…β€β™€οΈ Also, [RFC3253], Section 3.6, says that if the Depth header is missing, it defaults to "0". πŸ‘‘

The response body for a successful request MUST be a DAV:multistatus XML element. If there are no response elements, the returned multistatus XML element is empty. 🫧

multistatus: see RFC 2518, Section 12.9

The response body for a successful DAV:principal-property-search REPORT request MUST contain a DAV:response element for each principal whose property values satisfy the search specification given in DAV:principal-property-search. πŸ¦‹

If DAV:prop is specified in the request body, the properties in the DAV:prop element MUST be reported in the DAV:response elements. πŸ’œ

Preconditions:

None

Postconditions:

(DAV:number-of-matches-within-limits): The number of matching principals must fall within server-specific, predefined limits. 😭 For example, this condition might be triggered if a search would return an extremely large number of responses.

9.4.1. Matching πŸ”

there are a few cases to think about when matching strings. The easiest one is when a property value is "simple", meaning it only has character information item content (see [REC-XML-INFOSET]). For example, the search string "julian" would match the DAV:displayname property with the value "Julian Reschke". 🌷 On the wire, DAV:displayname in this case is marshalled as:

<D:displayname xmlns:D="DAV:">Julian Reschke</D:displayname>

The property's name is encoded into the XML element information item, and the character information item content of the property is "Julian Reschke".

A more complicated case is when a property has mixed content, which means compound values made of multiple child element items, other kinds of information items, and character information item content. πŸŒ€ Take the property "aprop" in the namespace "http://www.example.com/props/", marshalled as:

<W:aprop xmlns:W="http://www.example.com/props/">
  {cdata 0}<W:elem1>{cdata 1}</W:elem1>
  <W:elem2>{cdata 2}</W:elem2>{cdata 3}
</W:aprop>

In this case, matching happens on each individual contiguous sequence of character information items. In the example above, a search string gets compared against these four strings:

{cdata 0}
{cdata 1}
{cdata 2}
{cdata 3}

So that's four separate matches, one each for {cdata 0}, {cdata 1}, {cdata 2}, and {cdata 3}. 🫢

9.4.2. Example: successful DAV:principal-property-search REPORT πŸ₯³

in this example, the client asks for the principal URLs of every user whose DAV:displayname contains the substring "doE" and whose "title" property in the namespace "http://BigCorp.com/ns/" (their professional title) contains "Sales". πŸ’Ό On top of that, the client asks for five properties to come back with each matching principal:

In the DAV: namespace: displayname

In the http://www.example.com/ns/ namespace: department, phone, office, salary

The response shows that two principal resources match the search, "John Doe" and "Zygdoebert Smith". πŸ‘€ The "salary" property in the namespace "http://www.example.com/ns/" isn't returned, because the principal making the request doesn't have enough access rights to read it. πŸ”’

REPORT /users/ HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset=utf-8
Content-Length: xxxx
Depth: 0

<?xml version="1.0" encoding="utf-8" ?>
<D:principal-property-search xmlns:D="DAV:">
  <D:property-search>
    <D:prop>
      <D:displayname/>
    </D:prop>
    <D:match>doE</D:match>
  </D:property-search>
  <D:property-search>
    <D:prop xmlns:B="http://www.example.com/ns/">
      <B:title/>
    </D:prop>
    <D:match>Sales</D:match>
  </D:property-search>
  <D:prop xmlns:B="http://www.example.com/ns/">
    <D:displayname/>
    <B:department/>
    <B:phone/>
    <B:office/>
    <B:salary/>
  </D:prop>
</D:principal-property-search>
HTTP/1.1 207 Multi-Status
Content-Type: text/xml; charset=utf-8
Content-Length: xxxx

<?xml version="1.0" encoding="utf-8" ?>
<D:multistatus xmlns:D="DAV:" xmlns:B="http://BigCorp.com/ns/">
  <D:response>
    <D:href>http://www.example.com/users/jdoe</D:href>
    <D:propstat>

      <D:prop>
        <D:displayname>John Doe</D:displayname>
        <B:department>Widget Sales</B:department>
        <B:phone>234-4567</B:phone>
        <B:office>209</B:office>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <B:salary/>
      </D:prop>
      <D:status>HTTP/1.1 403 Forbidden</D:status>
    </D:propstat>
  </D:response>
  <D:response>
    <D:href>http://www.example.com/users/zsmith</D:href>
    <D:propstat>
      <D:prop>
        <D:displayname>Zygdoebert Smith</D:displayname>
        <B:department>Gadget Sales</B:department>
        <B:phone>234-7654</B:phone>
        <B:office>114</B:office>
      </D:prop>
      <D:status>HTTP/1.1 200 OK</D:status>
    </D:propstat>
    <D:propstat>
      <D:prop>
        <B:salary/>
      </D:prop>
      <D:status>HTTP/1.1 403 Forbidden</D:status>
    </D:propstat>
  </D:response>
</D:multistatus>

9.5. DAV:principal-search-property-set REPORT ✨

Okay bestie, this REPORT is how you find out which properties you can actually search with the DAV:principal-property-search REPORT from Section 9.4 πŸ” It's basically the menu of search options πŸ’…

Servers MUST support the DAV:principal-search-property-set REPORT on every collection listed in the value of a DAV:principal-collection-set property 🫢

A user agent could use the results of this REPORT to build a search box so people can look up principals πŸ”Ž Iconic.

Supporting this report is REQUIRED πŸ’–

Implementation Note: Some clients have really limited screen space for listing searchable properties πŸ“± In that case, a user would probably love having the most-searched properties show up on-screen instead of making them scroll through a giant list. One way to signal which ones are the most popular is to put them near the start of the list. Then a client can display the list in order, so the popular properties are likely to be on-screen and don't need any scrolling to pick πŸ™Œ

Marshalling:

The request body MUST be an empty DAV:principal-search-property-set XML element πŸŽ€

This report is only defined when the Depth header has the value "0". Any other value gets a 400 (Bad Request) error response πŸ™…β€β™€οΈ Also, [RFC3253], Section 3.6, says that if the Depth header isn't there at all, it defaults to "0", so you're not stuck guessing πŸ€“

The response body MUST be a DAV:principal-search-property-set XML element πŸ“¦ It contains one DAV:principal-search-property XML element for each property that can be searched with the DAV:principal-property-search REPORT. A server MAY limit its response to just a subset of the searchable properties, like the ones an interactive access control client is most likely to need πŸ’œ

<!ELEMENT principal-search-property-set
 (principal-search-property*) >

Each DAV:principal-search-property XML element holds exactly one searchable property plus a description of it 🌸

<!ELEMENT principal-search-property (prop, description) >

The DAV:prop element holds one principal property that the server can use for a DAV:principal-property-search REPORT ✨

prop: see RFC 2518, Section 12.11

The description element is a human-readable explanation of what the property represents πŸ“ Servers MUST indicate the human language of the description using the xml:lang attribute, and SHOULD consider the HTTP Accept-Language request header when picking one of several available languages 🌈

<!ELEMENT description #PCDATA >

9.5.1. Example: DAV:principal-search-property-set REPORT

Let's see it in action πŸ¦‹ In this example, the client finds out which principal properties are searchable by sending the DAV:principal-search-property-set REPORT to the root of the server's principal URL collection set, which is http://www.example.com/users/.

Request <<
REPORT /users/ HTTP/1.1
Host: www.example.com
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx
Accept-Language: en, de
Authorization: BASIC d2FubmFtYWs6cGFzc3dvcmQ=
Depth: 0

<?xml version="1.0" encoding="utf-8" ?>
<D:principal-search-property-set xmlns:D="DAV:"/>
Response <<
HTTP/1.1 200 OK
Content-Type: text/xml; charset="utf-8"
Content-Length: xxx

<?xml version="1.0" encoding="utf-8" ?>
<D:principal-search-property-set xmlns:D="DAV:">
  <D:principal-search-property>
    <D:prop>
      <D:displayname/>
    </D:prop>
    <D:description xml:lang="en">Full name</D:description>
  </D:principal-search-property>
  <D:principal-search-property>
    <D:prop xmlns:B="http://BigCorp.com/ns/">
      <B:title/>

    </D:prop>
    <D:description xml:lang="en">Job title</D:description>
  </D:principal-search-property>
</D:principal-search-property-set>

10. XML Processing 🧩

Implementations of this specification MUST support the XML element ignore rule, as specified in Section 23.3.2 of [RFC2518], and the XML Namespace recommendation [REC-XML-NAMES] πŸ™Œ

Also, the DAV namespace is reserved for XML elements and property names that are defined in a standards-track or Experimental IETF RFC. So don't just make up stuff in there πŸ™…β€β™€οΈ

11. Internationalization Considerations 🌍

Okay so in this spec, the only human-readable stuff is the description XML element, which lives inside the DAV:supported-privilege-set property πŸ’¬ It explains what the capabilities controlled by a privilege actually do. That means the description element has to be able to hold descriptions in multiple character sets. Since it's inside a WebDAV property, it goes over the wire as XML [REC-XML], so it gets to use XML's language tagging and character set encoding. Honestly, the glow-up is real ✨

XML processors at minimum must be able to read XML elements encoded with the UTF-8 [RFC3629] encoding of the ISO 10646 multilingual plane 🌏 The XML examples in this spec show the charset parameter of the Content-Type header, defined in [RFC3023], along with the XML "encoding" attribute. Together they tell both MIME and XML processors what charset is being used πŸ€“ This spec also requires server implementations to tag description fields with the xml:lang attribute (see Section 2.12 of [REC-XML]), which says what human language the description is in. On top of that, server implementations should look at the Accept-Language HTTP header to figure out which description string to send back 🌸

For XML elements other than the description element, implementations are expected to treat property names, privilege names, and values as tokens. Then they convert those tokens into readable text in the user's language and character set before showing them to a person πŸ’… Only a generic WebDAV property display utility would show these raw values to a human πŸ™ˆ

For error reporting, we follow the HTTP/1.1 status code convention. Each status code comes with a short English description, like 200 (OK) πŸ“£ A poorly made user agent might show that message to users, but internationalized apps will ignore it and show an appropriate message in the user's language and character set 🫢

Further internationalization considerations for this protocol are described in the WebDAV Distributed Authoring protocol specification [RFC2518] πŸ“š

12. Security Considerations πŸ”

Bestie, this part is serious 😭 Applications and users of this access control protocol should be aware of several security considerations, which are detailed below. On top of what's in this document, you should also consider the security considerations in the HTTP/1.1 specification [RFC2616], the WebDAV Distributed Authoring Protocol specification [RFC2518], and the XML Media Types specification [RFC3023] in any security analysis of this protocol πŸ›‘οΈ

12.1. Increased Risk of Compromised Users 🚨

Here's the tea. Without a way to change ACLs remotely, if one user's login gets compromised, the attacker can only read, modify, move, or delete the resources that user already had access to πŸ”’ But once this access control protocol exists, if one compromised user can change ACLs for a whole bunch of other users (like a super-user), then a single compromised account can mess with way more resources. Yikes πŸ’€ You can reduce this risk by limiting how many people have write-acl privileges across a broad range of resources πŸ’œ

12.2. Risks of the DAV:read-acl and DAV:current-user-privilege-set Privileges πŸ‘€

Reading access privileges might seem harmless, because reading an ACL can't possibly change the resource's state πŸ™ƒ Like, the access privileges (stored in the DAV:acl property), or the privileges the currently authenticated user has (stored in the DAV:current-user-privilege-set property), feel innocent. BUT, if every resource has world-readable ACLs, an attacker can run an exhaustive search for resources that accidentally left themselves vulnerable, like being world-writable 😱 The property retrieval method PROPFIND, run with Depth infinity on an entire hierarchy, is a super efficient way to grab the DAV:acl or DAV:current-user-privilege-set properties. Once they find an open resource, they can use it for a denial of service attack by overwriting it over and over πŸ’₯ Or they can just modify writable resources in sneaky, undesirable ways πŸ™ˆ

To reduce this risk, read-acl privileges should not be granted to unauthenticated principals 🚫 Also, restrictions on read-acl and read-current-user-privilege-set privileges for authenticated principals should be carefully analyzed when you deploy this protocol 🧐 Access to the current-user-privilege-set property is a tradeoff between usability and security. When the current-user-privilege-set is visible, user interfaces can show extra info about permitted and restricted operations, but that same info might also reveal a vulnerability someone could exploit 🫠 So deployments need to weigh this tradeoff against what their environment actually requires πŸ’…

12.3. No Foreknowledge of Initial ACL πŸ₯Ί

To keep the protocol simpler, this spec intentionally doesn't cover how to manage or discover the initial ACL that gets put on a resource when it's created πŸ€·β€β™€οΈ The only way to find the initial ACL is to create a new resource and then fetch the DAV:acl property. And that only works if the principal who made the resource was also granted the DAV:read-acl privilege πŸ”

So a principal could create a resource and then find out its ACL grants privileges they don't want 😬 Even worse, the protocol makes it possible (though unlikely) that the creating principal can't modify the ACL, or even delete the resource πŸ’€ And even when the ACL can be changed, there's a short window where the resource exists with the initial ACL before its new one gets set ⏳

A few things help with this risk. Human principals are often aware of the default access permissions in their editing environments, and they factor that into what they write πŸ“ Also, default privilege policies are usually really conservative, which limits the privileges the initial ACL grants 🫢

13. Authentication πŸ”‘

Authentication mechanisms defined for HTTP and WebDAV also apply to this WebDAV Access Control Protocol 🌸 That includes the Basic and Digest authentication mechanisms defined in [RFC2617]. Implementing the ACL spec requires that Basic authentication, if it's used, MUST only be supported over a secure transport such as TLS πŸ” No plaintext vibes allowed πŸ™…β€β™€οΈ

14. IANA Considerations ✨

okay so IANA stuff πŸ’… this spec uses the XML element namespace that [RFC2518] already set up, which is the DAV: URI namespace. It was registered earlier in the URI schemes registry, so no new drama 🌸 Everything else [RFC2518] says about IANA considerations also applies here 🫢

15. Acknowledgements

this protocol is a group project from the WebDAV ACL design team πŸ™Œ the team is Bernard Chester, Geoff Clemm, Anne Hopkins, Barry Lind, Sean Lyndersay, Eric Sedlar, Greg Stein, and Jim Whitehead. the authors are super grateful for the detailed reviews and comments from Jim Amsden, Dylan Barrell, Gino Basso, Murthy Chintalapati, Lisa Dusseault, Stefan Eissing, Tim Ellison, Yaron Goland, Dennis Hamilton, Laurie Harper, Eckehard Hermann, Ron Jacobs, Chris Knight, Remy Maucherat, Larry Masinter, Joe Orton, Peter Raymond, and Keith Wannamaker πŸ’–

Keith Wannamaker wrote the initial text for the principal property search sections, so he gets a whole extra crown πŸ‘‘ Earlier work on WebDAV access control protocols came from Yaron Goland, Paul Leach, Lisa Dusseault, Howard Palmer, and Jon Radoff ✨ and the authors also want to shout out the foundation laid by the DeltaV, WebDAV and HTTP protocol authors, which this protocol is built on, plus the feedback from the WebDAV working group πŸ¦‹

16. References

16.1. Normative References

  • [REC-XML] Bray, T., Paoli, J., Sperberg-McQueen, C. and E. Maler, "Extensible Markup Language (XML) 1.0 ((Third ed)", W3C REC REC-xml-20040204, February 2004, <http://www.w3.org/TR/2004/REC-xml-20040204>.
  • [REC-XML-INFOSET] Cowan, J. and R. Tobin, "XML Information Set (Second Edition)", W3C REC REC-xml-infoset-20040204, February 2004, <http://www.w3.org/TR/2004/REC-xml-infoset-20040204/>.
  • [REC-XML-NAMES] Bray, T., Hollander, D. and A. Layman, "Namespaces in XML", W3C REC REC-xml-names-19990114, January 1999, <http://www.w3.org/TR/1999/REC-xml-names-19990114>.
  • [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
  • [RFC2518] Goland, Y., Whitehead, E., Faizi, A., Carter, S. and D. Jensen, "HTTP Extensions for Distributed Authoring -- WEBDAV", RFC 2518, February 1999.
  • [RFC2616] Fielding, R., Gettys, J., Mogul, J., Frystyk, H., Masinter, L., Leach, P. and T. Berners-Lee, "Hypertext Transfer Protocol -- HTTP/1.1", RFC 2616, June 1999.
  • [RFC2617] Franks, J., Hallam-Baker, P., Hostetler, J., Lawrence, S., Leach, P., Luotonen, A. and L. Stewart, "HTTP Authentication: Basic and Digest Access Authentication", RFC 2617, June 1999.
  • [RFC3023] Murata, M., St.Laurent, S. and D. Kohn, "XML Media Types", RFC 3023, January 2001.
  • [RFC3253] Clemm, G., Amsden, J., Ellison, T., Kaler, C. and J. Whitehead, "Versioning Extensions to WebDAV", RFC 3253, March 2002.
  • [RFC3530] Shepler, S., Ed., Callaghan, B., Robinson, D., Thurlow, R., Beame, C., Eisler, M. and D. Noveck, "Network File System (NFS) version 4 Protocol", RFC 3530, April 2003.
  • [RFC3629] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629 November 2003.

16.2. Informative References

Appendix A. WebDAV XML Document Type Definition Addendum

all the XML elements in this Document Type Definition (DTD) belong to the DAV namespace πŸŽ€ think of this DTD as an add-on to the DTD in [RFC2518], section 23.1 ✨

<!-- Privileges -- (Section 3)>

<!ELEMENT read EMPTY>
<!ELEMENT write EMPTY>
<!ELEMENT write-properties EMPTY>
<!ELEMENT write-content EMPTY>
<!ELEMENT unlock EMPTY>
<!ELEMENT read-acl EMPTY>
<!ELEMENT read-current-user-privilege-set EMPTY>
<!ELEMENT write-acl EMPTY>
<!ELEMENT bind EMPTY>
<!ELEMENT unbind EMPTY>
<!ELEMENT all EMPTY>

<!-- Principal Properties (Section 4) -->

<!ELEMENT principal EMPTY>

<!ELEMENT alternate-URI-set (href*)>
<!ELEMENT principal-URL (href)>
<!ELEMENT group-member-set (href*)>
<!ELEMENT group-membership (href*)>

<!-- Access Control Properties (Section 5) -->

<!-- DAV:owner Property (Section 5.1) -->

<!ELEMENT owner (href?)>

<!-- DAV:group Property (Section 5.2) -->

<!ELEMENT group (href?)>

<!-- DAV:supported-privilege-set Property (Section 5.3) -->

<!ELEMENT supported-privilege-set (supported-privilege*)>
<!ELEMENT supported-privilege
 (privilege, abstract?, description, supported-privilege*)>

<!ELEMENT privilege ANY>
<!ELEMENT abstract EMPTY>
<!ELEMENT description #PCDATA>
<!-- DAV:current-user-privilege-set Property (Section 5.4) -->

<!ELEMENT current-user-privilege-set (privilege*)>

<!-- DAV:acl Property (Section 5.5) -->

<!ELEMENT acl (ace)* >
<!ELEMENT ace ((principal | invert), (grant|deny), protected?,
 inherited?)>

<!ELEMENT principal (href)
 | all | authenticated | unauthenticated
 | property | self)>

<!ELEMENT all EMPTY>
<!ELEMENT authenticated EMPTY>
<!ELEMENT unauthenticated EMPTY>
<!ELEMENT property ANY>
<!ELEMENT self EMPTY>

<!ELEMENT invert principal>

<!ELEMENT grant (privilege+)>
<!ELEMENT deny (privilege+)>
<!ELEMENT privilege ANY>

<!ELEMENT protected EMPTY>

<!ELEMENT inherited (href)>

<!-- DAV:acl-restrictions Property (Section 5.6) -->

<!ELEMENT acl-restrictions (grant-only?, no-invert?,
 deny-before-grant?, required-principal?)>

<!ELEMENT grant-only EMPTY>
<!ELEMENT no-invert EMPTY>
<!ELEMENT deny-before-grant EMPTY>

<!ELEMENT required-principal
 (all? | authenticated? | unauthenticated? | self? | href*
 |property*)>

<!-- DAV:inherited-acl-set Property (Section 5.7) -->

<!ELEMENT inherited-acl-set (href*)>

<!-- DAV:principal-collection-set Property (Section 5.8) -->
<!ELEMENT principal-collection-set (href*)>

<!-- Access Control and Existing Methods (Section 7) -->

<!ELEMENT need-privileges (resource)* >
<!ELEMENT resource ( href, privilege )

<!-- ACL method preconditions (Section 8.1.1) -->

<!ELEMENT no-ace-conflict EMPTY>
<!ELEMENT no-protected-ace-conflict EMPTY>
<!ELEMENT no-inherited-ace-conflict EMPTY>
<!ELEMENT limited-number-of-aces EMPTY>
<!ELEMENT grant-only EMPTY>
<!ELEMENT no-invert EMPTY>
<!ELEMENT deny-before-grant EMPTY>
<!ELEMENT no-abstract EMPTY>
<!ELEMENT not-supported-privilege EMPTY>
<!ELEMENT missing-required-principal EMPTY>
<!ELEMENT recognized-principal EMPTY>
<!ELEMENT allowed-principal EMPTY>

<!-- REPORTs (Section 9) -->

<!ELEMENT acl-principal-prop-set ANY>
ANY value: a sequence of one or more elements, with at most one
DAV:prop element.

<!ELEMENT principal-match ((principal-property | self), prop?)>
<!ELEMENT principal-property ANY>
ANY value: an element whose value identifies a property. The
expectation is the value of the named property typically contains
an href element that contains the URI of a principal
<!ELEMENT self EMPTY>

<!ELEMENT principal-property-search ((property-search+), prop?) >
<!ELEMENT property-search (prop, match) >
<!ELEMENT match #PCDATA >

<!ELEMENT principal-search-property-set (
 principal-search-property*) >
<!ELEMENT principal-search-property (prop, description) >
<!ELEMENT description #PCDATA >

Appendix B. WebDAV Method Privilege Table (Normative) ✨

okay bestie, this table is basically the cheat sheet for which privileges each WebDAV method needs πŸ“‹πŸ’… it covers the methods from RFC 2518, 2616, and 3253. the big rule is that if one of the listed privileges is denied, access MUST be denied 🚫 but having all of them doesn't guarantee access πŸ™…β€β™€οΈ because an implementation MAY define an extra custom privilege that gates existing methods. and lacking the listed privileges doesn't automatically mean denied either, since a sub-privilege aggregated under the listed one might be what's actually doing the gating πŸ€·β€β™€οΈ also, unless it says otherwise, the privileges refer to the current resource being processed πŸŽ€

+---------------------------------+---------------------------------+
| METHOD                          | PRIVILEGES                      |
+---------------------------------+---------------------------------+
| GET                             | <D:read>                        |
| HEAD                            | <D:read>                        |
| OPTIONS                         | <D:read>                        |
| PUT (target exists)             | <D:write-content> on target     |
|                                 | resource                        |
| PUT (no target exists)          | <D:bind> on parent collection   |
|                                 | of target                       |
| PROPPATCH                       | <D:write-properties>            |
| ACL                             | <D:write-acl>                   |
| PROPFIND                        | <D:read> (plus <D:read-acl> and |
|                                 | <D:read-current-user-privilege- |
|                                 | set> as needed)                 |
| COPY (target exists)            | <D:read>, <D:write-content> and |
|                                 | <D:write-properties> on target  |
|                                 | resource                        |
| COPY (no target exists)         | <D:read>, <D:bind> on target    |
|                                 | collection                      |
| MOVE (no target exists)         | <D:unbind> on source collection |
|                                 | and <D:bind> on target          |
|                                 | collection                      |
| MOVE (target exists)            | As above, plus <D:unbind> on    |
|                                 | the target collection           |
| DELETE                          | <D:unbind> on parent collection |
| LOCK (target exists)            | <D:write-content>               |
| LOCK (no target exists)         | <D:bind> on parent collection   |
| MKCOL                           | <D:bind> on parent collection   |
| UNLOCK                          | <D:unlock>                      |
| CHECKOUT                        | <D:write-properties>            |
| CHECKIN                         | <D:write-properties>            |
| REPORT                          | <D:read> (on all referenced     |
|                                 | resources)                      |
| VERSION-CONTROL                 | <D:write-properties>            |
| MERGE                           | <D:write-content>               |
| MKWORKSPACE                     | <D:write-content> on parent     |
|                                 | collection                      |
| BASELINE-CONTROL                | <D:write-properties> and        |
|                                 | <D:write-content>               |
| MKACTIVITY                      | <D:write-content> on parent     |
|                                 | collection                      |
+---------------------------------+---------------------------------+

Index πŸ—‚οΈ

   A
      ACL method  40

   C
      Condition Names
         DAV:allowed-principal (pre)  42
         DAV:deny-before-grant (pre)  41
         DAV:grant-only (pre)  41
         DAV:limited-number-of-aces (pre)  41
         DAV:missing-required-principal (pre)  42
         DAV:no-abstract (pre)  41
         DAV:no-ace-conflict (pre)  41
         DAV:no-inherited-ace-conflict (pre)  41
         DAV:no-invert (pre)  41
         DAV:no-protected-ace-conflict (pre)  41
         DAV:not-supported-privilege (pre)  42
         DAV:number-of-matches-within-limits (post)  48, 53
         DAV:recognized-principal (pre)  42

   D
      DAV header
         compliance class 'access-control'  38
      DAV:acl property  23
      DAV:acl-principal-prop-set report  48
      DAV:acl-restrictions property  27
      DAV:all privilege  13
      DAV:allowed-principal precondition  42
      DAV:alternate-URI-set property  14
      DAV:bind privilege  12
      DAV:current-user-privilege-set property  21
      DAV:deny-before-grant precondition  41
      DAV:grant-only precondition  41
      DAV:group property  18
      DAV:group-member-set property  14
      DAV:group-membership property  14
      DAV:inherited-acl-set property  29
      DAV:limited-number-of-aces precondition  41
      DAV:missing-required-principal precondition  42
      DAV:no-abstract precondition  41
      DAV:no-ace-conflict precondition  41
      DAV:no-inherited-ace-conflict precondition  41
      DAV:no-invert precondition  41
      DAV:no-protected-ace-conflict precondition  41
      DAV:not-supported-privilege precondition  42
      DAV:number-of-matches-within-limits postcondition  48, 53
      DAV:owner property  15
      DAV:principal resource type  13
      DAV:principal-collection-set property  30
      DAV:principal-match report  50
      DAV:principal-property-search  51
      DAV:principal-search-property-set  56
      DAV:principal-URL property  14
      DAV:read privilege  10
      DAV:read-acl privilege  11
      DAV:read-current-user-privilege-set privilege  12
      DAV:recognized-principal precondition  42
      DAV:supported-privilege-set property  18
      DAV:unbind privilege  12
      DAV:unlock privilege  11
      DAV:write privilege  10
      DAV:write-acl privilege  12
      DAV:write-content privilege  10
      DAV:write-properties privilege  10

   M
      Methods
         ACL  40

   P
      Privileges
         DAV:all  13
         DAV:bind  12
         DAV:read  10
         DAV:read-acl  11
         DAV:read-current-user-privilege-set  12
         DAV:unbind  12
         DAV:unlock  11
         DAV:write  10
         DAV:write-acl  12
         DAV:write-content  11
         DAV:write-properties  10
      Properties
         DAV:acl  23
         DAV:acl-restrictions  27
         DAV:alternate-URI-set  14
         DAV:current-user-privilege-set  21
         DAV:group  18
         DAV:group-member-set  14
         DAV:group-membership  14
         DAV:inherited-acl-set  29
         DAV:owner  15
         DAV:principal-collection-set  30
         DAV:principal-URL  14
         DAV:supported-privilege-set  18

   R
      Reports
         DAV:acl-principal-prop-set  47
         DAV:principal-match  49
         DAV:principal-property-search  51
         DAV:principal-search-property-set  56
      Resource Types
         DAV:principal  13

Authors' Addresses πŸ–‹οΈ

   Geoffrey Clemm
   IBM
   20 Maguire Road
   Lexington, MA  02421

   EMail: geoffrey.clemm@us.ibm.com


   Julian F. Reschke
   greenbytes GmbH
   Salzmannstrasse 152
   Muenster, NW  48159
   Germany

   EMail: julian.reschke@greenbytes.de


   Eric Sedlar
   Oracle Corporation
   500 Oracle Parkway
   Redwood Shores, CA  94065

   EMail: eric.sedlar@oracle.com


   Jim Whitehead
   U.C. Santa Cruz, Dept. of Computer Science
   1156 High Street
   Santa Cruz, CA  95064

   EMail: ejw@cse.ucsc.edu

Copyright (C) The Internet Society (2004). this document is subject to the rights, licenses and restrictions in BCP 78, and except as set out there, the authors keep all their rights πŸ”

this document and the info in it are provided "AS IS", and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED. that includes, but isn't limited to, any warranty that using the info won't infringe any rights, and any implied warranties of merchantability or fitness for a particular purpose πŸ’€

Intellectual Property 🧠

the IETF takes no position on whether any Intellectual Property Rights or other rights that might be claimed to cover the technology in this document are valid or in scope, or on whether any license under those rights might or might not be available. it also doesn't claim to have made any independent effort to find such rights. info about how rights in RFC documents are handled is in BCP 78 and BCP 79 πŸ“š

copies of IPR disclosures sent to the IETF Secretariat, and any assurances of licenses to be made available, or the result of an attempt to get a general license or permission to use those proprietary rights for implementers or users of this spec, can be found in the IETF online IPR repository at http://www.ietf.org/ipr πŸ”—

the IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology required to implement this standard. send that info to the IETF at ietf-ipr@ietf.org 🫢

Acknowledgement 🌸

funding for the RFC Editor function is currently provided by the Internet Society ✨

πŸ“¦ where this page comes from