carddav: acl readonly (for Thunderbird)

This commit is contained in:
oliverpool
2024-10-10 20:36:03 +02:00
committed by Filip Góra
parent 9c900b1c66
commit 1d5a5dcd6c
5 changed files with 897 additions and 8 deletions

View File

@@ -28,6 +28,7 @@ type AddressBook struct {
Description string
MaxResourceSize int64
SupportedAddressData []AddressDataType
ReadOnly bool
}
func (ab *AddressBook) SupportsAddressData(contentType, version string) bool {
@@ -107,6 +108,7 @@ type AddressObject struct {
ContentLength int64
ETag string
Card vcard.Card
ReadOnly bool
}
// SyncQuery is the query struct represents a sync-collection request

View File

@@ -496,13 +496,23 @@ func (b *backend) propFindAddressBook(ctx context.Context, propfind *internal.Pr
}
return &internal.CurrentUserPrincipal{Href: internal.Href{Path: path}}, nil
},
internal.ResourceTypeName: internal.PropFindValue(internal.NewResourceType(internal.CollectionName, addressBookName)),
supportedAddressDataName: internal.PropFindValue(&supportedAddressData{
internal.ResourceTypeName: func(*internal.RawXMLValue) (interface{}, error) {
return internal.NewResourceType(internal.CollectionName, addressBookName), nil
},
supportedAddressDataName: func(*internal.RawXMLValue) (interface{}, error) {
return &supportedAddressData{
Types: []addressDataType{
{ContentType: vcard.MIMEType, Version: "3.0"},
{ContentType: vcard.MIMEType, Version: "4.0"},
},
}),
}, nil
},
internal.CurrentUserPrivilegeSetName: func(*internal.RawXMLValue) (interface{}, error) {
if ab.ReadOnly {
return &internal.CurrentUserPrivilegeSetReadOnly, nil
}
return &internal.CurrentUserPrivilegeSetReadWrite, nil
},
}
if ab.Name != "" {
@@ -569,6 +579,12 @@ func (b *backend) propFindAddressObject(ctx context.Context, propfind *internal.
return &addressDataResp{Data: buf.Bytes()}, nil
},
internal.CurrentUserPrivilegeSetName: func(*internal.RawXMLValue) (interface{}, error) {
if ao.ReadOnly {
return &internal.CurrentUserPrivilegeSetReadOnly, nil
}
return &internal.CurrentUserPrivilegeSetReadWrite, nil
},
}
if ao.ContentLength > 0 {

548
internal/acl.go Normal file
View File

@@ -0,0 +1,548 @@
package internal
import (
"encoding/xml"
)
func NewPrivilege(name xml.Name) Privilege {
return Privilege{
Raw: NewRawXMLElement(name, nil, nil),
}
}
type Privilege struct {
XMLName xml.Name `xml:"DAV: privilege"`
Raw *RawXMLValue `xml:",any"`
}
func (p Privilege) Is(target xml.Name) bool {
got, ok := p.Raw.XMLName()
return ok && got == target
}
var (
/*
rfc3744#section-3.1
The read privilege controls methods that return information about the
state of the resource, including the resource's properties. Affected
methods include GET and PROPFIND. Any implementation-defined
privilege that also controls access to GET and PROPFIND must be
aggregated under DAV:read - if an ACL grants access to DAV:read, the
client may expect that no other privilege needs to be granted to have
access to GET and PROPFIND. Additionally, the read privilege MUST
control the OPTIONS method.
*/
Read = xml.Name{"DAV:", "read"}
/*
rfc3744#section-3.2
The write privilege controls methods that lock a resource or modify
the content, dead properties, or (in the case of a collection)
membership of the resource, such as PUT and PROPPATCH. Note that
state modification is also controlled via locking (see section 5.3 of
[RFC2518]), so effective write access requires that both write
privileges and write locking requirements are satisfied. Any
implementation-defined privilege that also controls access to methods
modifying content, dead properties or collection membership must be
aggregated under DAV:write, e.g., if an ACL grants access to
DAV:write, the client may expect that no other privilege needs to be
granted to have access to PUT and PROPPATCH.
*/
Write = xml.Name{"DAV:", "write"}
/*
rfc3744#section-3.3
The DAV:write-properties privilege controls methods that modify the
dead properties of the resource, such as PROPPATCH. Whether this
privilege may be used to control access to any live properties is
determined by the implementation. Any implementation-defined
privilege that also controls access to methods modifying dead
properties must be aggregated under DAV:write-properties - e.g., if
an ACL grants access to DAV:write-properties, the client can safely
expect that no other privilege needs to be granted to have access to
PROPPATCH.
*/
WriteProperties = xml.Name{"DAV:", "write-properties"}
/*
rfc3744#section-3.4
The DAV:write-content privilege controls methods that modify the
content of an existing resource, such as PUT. Any implementation-
defined privilege that also controls access to content must be
aggregated under DAV:write-content - e.g., if an ACL grants access to
DAV:write-content, the client can safely expect that no other
privilege needs to be granted to have access to PUT. Note that PUT -
when applied to an unmapped URI - creates a new resource and
therefore is controlled by the DAV:bind privilege on the parent
collection.
*/
WriteContent = xml.Name{"DAV:", "write-content"}
/*
rfc3744#section-3.5
The DAV:unlock privilege controls the use of the UNLOCK method by a
principal other than the lock owner (the principal that created a
lock can always perform an UNLOCK). While the set of users who may
lock a resource is most commonly the same set of users who may modify
a resource, servers may allow various kinds of administrators to
unlock resources locked by others. Any privilege controlling access
by non-lock owners to UNLOCK MUST be aggregated under DAV:unlock.
A lock owner can always remove a lock by issuing an UNLOCK with the
correct lock token and authentication credentials. That is, even if
a principal does not have DAV:unlock privilege, they can still remove
locks they own. Principals other than the lock owner can remove a
lock only if they have DAV:unlock privilege and they issue an UNLOCK
with the correct lock token. Lock timeout is not affected by the
DAV:unlock privilege.
*/
Unlock = xml.Name{"DAV:", "unlock"}
/*
rfc3744#section-3.6
The DAV:read-acl privilege controls the use of PROPFIND to retrieve
the DAV:acl property of the resource.
*/
ReadACL = xml.Name{"DAV:", "read-acl"}
/*
rfc3744#section-3.7
The DAV:read-current-user-privilege-set privilege controls the use of
PROPFIND to retrieve the DAV:current-user-privilege-set property of
the resource.
Clients are intended to use this property to visually indicate in
their UI items that are dependent on the permissions of a resource,
for example, by graying out resources that are not writable.
This privilege is separate from DAV:read-acl because there is a need
to allow most users access to the privileges permitted the current
user (due to its use in creating the UI), while the full ACL contains
information that may not be appropriate for the current authenticated
user. As a result, the set of users who can view the full ACL is
expected to be much smaller than those who can read the current user
privilege set, and hence distinct privileges are needed for each.
*/
ReadCurrentUserPrivilegeSet = xml.Name{"DAV:", "read-current-user-privilege-set"}
/*
rfc3744#section-3.8
The DAV:write-acl privilege controls use of the ACL method to modify
the DAV:acl property of the resource.
*/
WriteACL = xml.Name{"DAV:", "write-acl"}
/*
rfc3744#section-3.9
The DAV:bind privilege allows a method to add a new member URL to the
specified collection (for example via PUT or MKCOL). It is ignored
for resources that are not collections.
*/
Bind = xml.Name{"DAV:", "bind"}
/*
rfc3744#section-3.10
The DAV:unbind privilege allows a method to remove a member URL from
the specified collection (for example via DELETE or MOVE). It is
ignored for resources that are not collections.
*/
Unbind = xml.Name{"DAV:", "unbind"}
/*
rfc3744#section-3.11
DAV:all is an aggregate privilege that contains the entire set of
privileges that can be applied to the resource.
*/
All = xml.Name{"DAV:", "all"}
)
/*
rfc3744#section-4
rfc3744#section-5.5.1
The current user matches DAV:href only if that user is authenticated
as being (or being a member of) the principal identified by the URL
contained by that DAV:href.
The current user always matches DAV:all.
The current user matches DAV:authenticated only if authenticated.
The current user matches DAV:unauthenticated only if not
authenticated.
*/
type Principal struct {
XMLName xml.Name `xml:"DAV: principal"`
Raw *RawXMLValue `xml:",any"`
}
/*
rfc3744#section-4.1
This protected property, if non-empty, contains the URIs of network
resources with additional descriptive information about the
principal. This property identifies additional network resources
(i.e., it contains one or more URIs) that may be consulted by a
client to gain additional knowledge concerning a principal. One
expected use for this property is the storage of an LDAP [RFC2255]
scheme URL. A user-agent encountering an LDAP URL could use LDAP
[RFC2251] to retrieve additional machine-readable directory
information about the principal, and display that information in its
user interface. Support for this property is REQUIRED, and the value
is empty if no alternate URI exists for the principal.
*/
type AlternateURISet struct {
XMLName xml.Name `xml:"DAV: alternate-URI-set"`
Href []Href `xml:"href,omitempty"`
}
/*
rfc3744#section-4.2
A principal may have many URLs, but there must be one "principal URL"
that clients can use to uniquely identify a principal. This
protected property contains the URL that MUST be used to identify
this principal in an ACL request. Support for this property is
REQUIRED.
*/
type PrincipalURL struct {
XMLName xml.Name `xml:"DAV: principal-URL"`
Href Href `xml:"href,omitempty"`
}
/*
rfc3744#section-4.3
This property of a group principal identifies the principals that are
direct members of this group. Since a group may be a member of
another group, a group may also have indirect members (i.e., 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.
*/
type GroupMemberSet struct {
XMLName xml.Name `xml:"DAV: group-member-set"`
Href []Href `xml:"href,omitempty"`
}
/*
rfc3744#section-4.4
This protected property identifies the groups in which the principal
is directly a member. Note that a server may allow a group to be a
member of another group, in which case the DAV:group-membership of
those other groups would need to be queried in order to determine the
groups in which the principal is indirectly a member. Support for
this property is REQUIRED.
*/
type GroupMembership struct {
XMLName xml.Name `xml:"DAV: group-membership"`
Href []Href `xml:"href,omitempty"`
}
/*
rfc3744#section-5.1
This property identifies a particular principal as being the "owner"
of the resource. Since the owner of a resource often has special
access control capabilities (e.g., the owner frequently has permanent
DAV:write-acl privilege), clients might display the resource owner in
their user interface.
Servers MAY implement DAV:owner as protected property and MAY return
an empty DAV:owner element as property value in case no owner
information is available.
*/
type Owner struct {
XMLName xml.Name `xml:"DAV: owner"`
Href Href `xml:"href,omitempty"`
}
/*
rfc3744#section-5.2
This property identifies a particular principal as being the "group"
of the resource. This property is commonly found on repositories
that implement the Unix privileges model.
Servers MAY implement DAV:group as protected property and MAY return
an empty DAV:group element as property value in case no group
information is available.
*/
type Group struct {
XMLName xml.Name `xml:"DAV: group"`
Href Href `xml:"href,omitempty"`
}
/*
rfc3744#section-5.3
This is a protected property that identifies the privileges defined
for the resource.
*/
type SupportedPrivilegeSet struct {
XMLName xml.Name `xml:"DAV: supported-privilege-set"`
SupportedPrivilege []SupportedPrivilege `xml:"supported-privilege"`
}
/*
rfc3744#section-5.3
Each privilege appears as an XML element, where aggregate privileges
list as sub-elements all of the privileges that they aggregate.
*/
type SupportedPrivilege struct {
XMLName xml.Name `xml:"DAV: supported-privilege"`
Privilege Privilege `xml:"privilege"`
/*
Abstract will be nil if not set
rfc3744#section-5.3
An abstract privilege MUST NOT be used in an ACE for that resource.
Servers MUST fail an attempt to set an abstract privilege.
*/
Abstract *struct{} `xml:"abstract,omitempty"`
Description Description `xml:"description"`
SupportedPrivilege []SupportedPrivilege `xml:"supported-privilege"`
}
/*
rfc3744#section-5.3
A description is a human-readable description of what this 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 selecting one of multiple
available languages.
*/
type Description struct {
XMLName xml.Name `xml:"DAV: description"`
Text string `xml:",chardata"`
Lang string `xml:"lang,attr,omitempty"`
}
/*
rfc3744#section-5.4
DAV:current-user-privilege-set is a protected property containing the
exact set of privileges (as computed by the server) granted to the
currently authenticated HTTP user. Aggregate privileges and their
contained privileges are listed. A user-agent can use the value of
this property to adjust its user interface to make actions
inaccessible (e.g., by graying out a menu item or button) for which
the current principal does not have permission. This property is
also useful for determining what operations the current principal can
perform, without having to actually execute an operation.
*/
type CurrentUserPrivilegeSet struct {
XMLName xml.Name `xml:"DAV: current-user-privilege-set"`
Privilege []Privilege `xml:"privilege"`
}
// convenience CurrentUserPrivilegeSet
var (
CurrentUserPrivilegeSetReadOnly = CurrentUserPrivilegeSet{
Privilege: []Privilege{NewPrivilege(Read)},
}
CurrentUserPrivilegeSetReadWrite = CurrentUserPrivilegeSet{
Privilege: []Privilege{NewPrivilege(Read), NewPrivilege(Write)},
}
)
/*
rfc3744#section-5.5
This is a protected property that specifies the list of access
control entries (ACEs), which define what principals are to get what
privileges for this resource.
*/
type ACL struct {
XMLName xml.Name `xml:"DAV: acl"`
ACE []ACE `xml:"ace"`
}
/*
rfc3744#section-5.5
Each DAV:ace element specifies the set of privileges to be either
granted or denied to a single principal. If the DAV:acl property is
empty, no principal is granted any privilege.
*/
type ACE struct {
XMLName xml.Name `xml:"DAV: ace"`
/*
rfc3744#section-5.5.1
The DAV:principal element identifies the principal to which this ACE
applies.
*/
Principal Principal `xml:"principal,omitempty"`
Grant *Grant `xml:"grant,omitempty"`
Deny *Deny `xml:"deny,omitempty"`
}
/*
rfc3744#section-5.5.2
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 of the DAV:acl of a resource MUST only contain
non-abstract elements specified in the DAV:supported-privilege-set of
that resource.
*/
type Grant struct {
XMLName xml.Name `xml:"DAV: grant"`
Privilege []Privilege `xml:"privilege"`
}
/*
rfc3744#section-5.5.2
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 of the DAV:acl of a resource MUST only contain
non-abstract elements specified in the DAV:supported-privilege-set of
that resource.
*/
type Deny struct {
XMLName xml.Name `xml:"DAV: deny"`
Privilege []Privilege `xml:"privilege"`
}
// to be continued (5.5.2. & following)
///////////////////////////////////////////////
var (
/*
rfc3744#section-5.6.1
This element indicates that ACEs with deny clauses are not allowed.
*/
GrantOnly = xml.Name{"DAV:", "grant-only"}
/*
rfc3744#section-5.6.2
This element indicates that ACEs with the <invert> element are not
allowed.
*/
NoInvert = xml.Name{"DAV:", "no-invert"}
/*
rfc3744#section-5.6.3
This element indicates that all deny ACEs must precede all grant
ACEs.
*/
DenyBeforeGrant = xml.Name{"DAV:", "deny-before-grant"}
)
var (
/*
rfc3744#section-8.1.1
The ACEs submitted in the ACL request MUST NOT
conflict with each other. This is a catchall error code indicating
that an implementation-specific ACL restriction has been violated.
*/
NoACEConflict = xml.Name{"DAV:", "no-ace-conflict"}
/*
rfc3744#section-8.1.1
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 given principal, then it would not be consistent if the ACL
request submitted an ACE denying DAV:write to the same principal.
*/
NoProtectedACEConflict = xml.Name{"DAV:", "no-protected-ace-conflict"}
/*
rfc3744#section-8.1.1
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 given principal, then it would not
be consistent if the ACL request submitted an ACE denying DAV:write
to the same principal. Note that reporting of this error will be
implementation-dependent. Implementations MUST either report this
error or allow the ACE to be set, and then let normal ACE evaluation
rules determine whether the new ACE has any impact on the privileges
available to a specific principal.
*/
NoInheritedACEConflict = xml.Name{"DAV:", "no-inherited-ace-conflict"}
/*
rfc3744#section-8.1.1
The number of ACEs submitted in the ACL
request MUST NOT exceed the number of ACEs allowed on that resource.
However, ACL-compliant servers MUST support at least one ACE granting
privileges to a single principal, and one ACE granting privileges to
a group.
*/
LimitedNumberOfACEs = xml.Name{"DAV:", "limited-number-of-aces"}
// already defined above:
// DenyBeforeGrant
// GrantOnly
// NoInvert
/*
rfc3744#section-8.1.1
The ACL request MUST NOT attempt to grant or deny
an abstract privilege
*/
NoAbstract = xml.Name{"DAV:", "no-abstract"}
/*
rfc3744#section-8.1.1
The ACEs submitted in the ACL request
MUST be supported by the resource.
*/
NotSupportedPrivilege = xml.Name{"DAV:", "not-supported-privilege"}
/*
rfc3744#section-8.1.1
The result of the ACL request MUST
have at least one ACE for each principal identified in a
DAV:required-principal XML element in the ACL semantics of that
resource
*/
MissingRequiredPrincipal = xml.Name{"DAV:", "missing-required-principal"}
/*
rfc3744#section-8.1.1
Every principal URL in the ACL request
MUST identify a principal resource.
*/
RecognizedPrincipal = xml.Name{"DAV:", "recognized-principal"}
/*
rfc3744#section-8.1.1
The principals specified in the ACEs
submitted in the ACL request MUST be allowed as principals for the
resource. For example, a server where only authenticated principals
can access resources would not allow the DAV:all or
DAV:unauthenticated principals to be used in an ACE, since these
would allow unauthenticated access to resources.
*/
AllowedPrincipal = xml.Name{"DAV:", "allowed-principal"}
)

322
internal/acl_test.go Normal file
View File

@@ -0,0 +1,322 @@
package internal
import (
"encoding/xml"
"fmt"
"strings"
"testing"
)
func decodePropInsideMultiStatus(data []byte, v interface{}) error {
var ms MultiStatus
err := xml.Unmarshal(data, &ms)
if err != nil {
return err
}
if len(ms.Responses) != 1 {
return fmt.Errorf("expected 1 <response>, got %d", len(ms.Responses))
}
ps := ms.Responses[0].PropStats
if len(ps) != 1 {
return fmt.Errorf("expected 1 <propstat>, got %d", len(ps))
}
return ps[0].Prop.Decode(v)
}
func checkSupportedPrivilege(t *testing.T, sp SupportedPrivilege, privilege xml.Name, abstract bool, description string, children int) {
t.Helper()
if !sp.Privilege.Is(privilege) {
t.Errorf("expected %s, got %v", privilege, sp.Privilege.Raw)
}
if abstract {
if sp.Abstract == nil {
t.Errorf("missing expected <abstract>")
}
} else {
if sp.Abstract != nil {
t.Errorf("unexpected <abstract>")
}
}
if strings.TrimSpace(sp.Description.Text) != description {
t.Errorf("expected description %q, got %q", description, strings.TrimSpace(sp.Description.Text))
}
if strings.TrimSpace(sp.Description.Lang) != "en" {
t.Errorf("expected lang %q, got %q", "en", sp.Description.Lang)
}
if len(sp.SupportedPrivilege) != children {
t.Fatalf("expected %d <supported-privilege>, got %d", children, len(sp.SupportedPrivilege))
}
}
func TestACLMarshalling(t *testing.T) {
/* rfc3744#section-5.1.1 */
t.Run("owner", func(t *testing.T) {
var owner Owner
err := decodePropInsideMultiStatus([]byte(` <?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>`), &owner)
if err != nil {
t.Error(err)
}
if owner.Href.String() != "http://www.example.com/acl/users/gstein" {
t.Fatalf("expected http://www.example.com/acl/users/gstein, got %s", owner.Href.String())
}
})
/* rfc3744#section-5.3.1 */
t.Run("supported-privilege-set", func(t *testing.T) {
var sps SupportedPrivilegeSet
err := decodePropInsideMultiStatus([]byte(` <?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>`), &sps)
if err != nil {
t.Error(err)
}
if len(sps.SupportedPrivilege) != 1 {
t.Fatalf("expected 1 <supported-privilege>, got %d", len(sps.SupportedPrivilege))
}
sp := sps.SupportedPrivilege[0]
checkSupportedPrivilege(t, sp, All, true, "Any operation", 3)
checkSupportedPrivilege(t, sp.SupportedPrivilege[0], Read, false, "Read any object", 2)
checkSupportedPrivilege(t, sp.SupportedPrivilege[1], Write, false, "Write any object", 3)
checkSupportedPrivilege(t, sp.SupportedPrivilege[2], Unlock, false, "Unlock resource", 0)
checkSupportedPrivilege(t, sp.SupportedPrivilege[0].SupportedPrivilege[0], ReadACL, true, "Read ACL", 0)
checkSupportedPrivilege(t, sp.SupportedPrivilege[0].SupportedPrivilege[1], ReadCurrentUserPrivilegeSet, true, "Read current user privilege set property", 0)
checkSupportedPrivilege(t, sp.SupportedPrivilege[1].SupportedPrivilege[0], WriteACL, true, "Write ACL", 0)
checkSupportedPrivilege(t, sp.SupportedPrivilege[1].SupportedPrivilege[1], WriteProperties, false, "Write properties", 0)
checkSupportedPrivilege(t, sp.SupportedPrivilege[1].SupportedPrivilege[2], WriteContent, false, "Write resource content", 0)
sp = SupportedPrivilege{
Privilege: NewPrivilege(All),
Abstract: &struct{}{},
Description: Description{Text: "all"},
}
buf, err := xml.Marshal(sp)
if err != nil {
t.Error(err)
}
if want := "<supported-privilege xmlns=\"DAV:\"><privilege xmlns=\"DAV:\"><all xmlns=\"DAV:\"></all></privilege><abstract></abstract><description xmlns=\"DAV:\">all</description></supported-privilege>"; string(buf) != want {
t.Errorf("expected %q, got %q", want, buf)
}
sp = SupportedPrivilege{
Privilege: NewPrivilege(Read),
Abstract: nil,
Description: Description{Text: "read"},
}
buf, err = xml.Marshal(sp)
if err != nil {
t.Error(err)
}
if want := "<supported-privilege xmlns=\"DAV:\"><privilege xmlns=\"DAV:\"><read xmlns=\"DAV:\"></read></privilege><description xmlns=\"DAV:\">read</description></supported-privilege>"; string(buf) != want {
t.Errorf("expected %q, got %q", want, buf)
}
})
/* rfc3744#section-5.4.1 */
t.Run("current-user-privilege-set", func(t *testing.T) {
var cups CurrentUserPrivilegeSet
err := decodePropInsideMultiStatus([]byte(`<?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>`), &cups)
if err != nil {
t.Error(err)
}
if len(cups.Privilege) != 1 {
t.Fatalf("expected 1 <privilege>, got %d", len(cups.Privilege))
}
if !cups.Privilege[0].Is(Read) {
t.Fatalf("expected <read>, got %v", cups.Privilege[0].Raw)
}
})
/* rfc3744#section-5.5.5 */
t.Run("acl", func(t *testing.T) {
var acl ACL
err := decodePropInsideMultiStatus([]byte(`<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>`), &acl)
if err != nil {
t.Error(err)
}
if len(acl.ACE) != 2 {
t.Fatalf("expected 2 <ace>, got %d", len(acl.ACE))
}
{
ace := acl.ACE[0]
principalName, ok := ace.Principal.Raw.XMLName()
if want := (xml.Name{"DAV:", "href"}); !ok || principalName != want {
t.Fatalf("expected %s, got %s", want, principalName)
}
var href Href
err = ace.Principal.Raw.Decode(&href)
if err != nil {
t.Error(err)
}
if want := "http://www.example.com/acl/groups/maintainers"; href.String() != want {
t.Fatalf("expected %s, got %s", want, href.String())
}
if len(ace.Grant.Privilege) != 1 {
t.Fatalf("expected 1 <privilege>, got %d", len(ace.Grant.Privilege))
}
if !ace.Grant.Privilege[0].Is(Write) {
t.Fatalf("expected <write>, got %v", ace.Grant.Privilege[0].Raw)
}
}
{
ace := acl.ACE[1]
principalName, ok := ace.Principal.Raw.XMLName()
if want := (xml.Name{"DAV:", "all"}); !ok || principalName != want {
t.Fatalf("expected %s, got %s", want, principalName)
}
if len(ace.Grant.Privilege) != 1 {
t.Fatalf("expected 1 <privilege>, got %d", len(ace.Grant.Privilege))
}
if !ace.Grant.Privilege[0].Is(Read) {
t.Fatalf("expected <read>, got %v", ace.Grant.Privilege[0].Raw)
}
}
var ace ACE
ace.Principal.Raw = NewRawXMLElement(xml.Name{"DAV:", "authenticated"}, nil, nil)
ace.Grant = &Grant{
Privilege: []Privilege{
NewPrivilege(Read),
},
}
buf, err := xml.Marshal(ace)
if err != nil {
t.Error(err)
}
if want := "<ace xmlns=\"DAV:\"><principal xmlns=\"DAV:\"><authenticated xmlns=\"DAV:\"></authenticated></principal><grant xmlns=\"DAV:\"><privilege xmlns=\"DAV:\"><read xmlns=\"DAV:\"></read></privilege></grant></ace>"; string(buf) != want {
t.Errorf("expected %q, got %q", want, buf)
}
})
}

View File

@@ -22,6 +22,7 @@ var (
GetETagName = xml.Name{Namespace, "getetag"}
CurrentUserPrincipalName = xml.Name{Namespace, "current-user-principal"}
CurrentUserPrivilegeSetName = xml.Name{Namespace, "current-user-privilege-set"}
)
type Status struct {