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

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)
}
})
}