From ef81051f7d0a18709ce182bd706c1e4c5fc2a6e9 Mon Sep 17 00:00:00 2001 From: Laria Carolin Chabowski Date: Fri, 30 Jun 2017 22:56:07 +0200 Subject: Use ACLs in tree objects --- acl/acl.go | 21 ++++++++ objects/object_tree.go | 114 +++++++++++++++++++++++++++++++++++++++----- objects/object_tree_test.go | 59 +++++++++++++++++++---- objects/properties.go | 4 +- 4 files changed, 175 insertions(+), 23 deletions(-) diff --git a/acl/acl.go b/acl/acl.go index 544edde..d3ab912 100644 --- a/acl/acl.go +++ b/acl/acl.go @@ -31,6 +31,20 @@ func (p Perm) String() string { type QualifiedPerms map[string]Perm +func (a QualifiedPerms) Equals(b QualifiedPerms) bool { + if len(a) != len(b) { + return false + } + + for k, va := range a { + if vb, ok := b[k]; !ok || vb != va { + return false + } + } + + return true +} + type ACL struct { User, Group, Other, Mask QualifiedPerms } @@ -132,3 +146,10 @@ func ParseACL(s string) (ACL, error) { return acl, nil } + +func (a ACL) Equals(b ACL) bool { + return a.User.Equals(b.User) && + a.Group.Equals(b.Group) && + a.Other.Equals(b.Other) && + a.Mask.Equals(b.Mask) +} diff --git a/objects/object_tree.go b/objects/object_tree.go index 07c265c..2a8f982 100644 --- a/objects/object_tree.go +++ b/objects/object_tree.go @@ -3,6 +3,7 @@ package objects import ( "bufio" "bytes" + "code.laria.me/petrific/acl" "errors" "fmt" "sort" @@ -24,6 +25,9 @@ var treeEntryTypes = map[TreeEntryType]struct{}{ type TreeEntry interface { Type() TreeEntryType + ACL() acl.ACL + User() string + Group() string equalContent(TreeEntry) bool toProperties() properties } @@ -32,49 +36,93 @@ func compareTreeEntries(a, b TreeEntry) bool { return a.Type() == b.Type() && a.equalContent(b) } -type TreeEntryFile ObjectId +type TreeEntryBase struct { + acl acl.ACL + user, group string +} + +func (teb TreeEntryBase) ACL() acl.ACL { + return teb.acl +} + +func (teb TreeEntryBase) User() string { + return teb.user +} + +func (teb TreeEntryBase) Group() string { + return teb.group +} + +func (teb TreeEntryBase) toProperties() properties { + return properties{ + "acl": teb.acl.String(), + "user": teb.user, + "group": teb.group, + } +} + +func (a TreeEntryBase) equalContent(b TreeEntryBase) bool { + return a.acl.Equals(b.acl) && a.user == b.user && a.group == b.group +} + +type TreeEntryFile struct { + TreeEntryBase + Ref ObjectId +} func (tef TreeEntryFile) Type() TreeEntryType { return TETFile } func (tef TreeEntryFile) toProperties() properties { - return properties{"ref": ObjectId(tef).String()} + props := tef.TreeEntryBase.toProperties() + props["ref"] = tef.Ref.String() + return props } func (a TreeEntryFile) equalContent(_b TreeEntry) bool { b, ok := _b.(TreeEntryFile) - return ok && ObjectId(b).Equals(ObjectId(a)) + return ok && a.TreeEntryBase.equalContent(b.TreeEntryBase) && a.Ref.Equals(b.Ref) } -type TreeEntryDir ObjectId +type TreeEntryDir struct { + TreeEntryBase + Ref ObjectId +} func (ted TreeEntryDir) Type() TreeEntryType { return TETDir } func (ted TreeEntryDir) toProperties() properties { - return properties{"ref": ObjectId(ted).String()} + props := ted.TreeEntryBase.toProperties() + props["ref"] = ted.Ref.String() + return props } func (a TreeEntryDir) equalContent(_b TreeEntry) bool { b, ok := _b.(TreeEntryDir) - return ok && ObjectId(b).Equals(ObjectId(a)) + return ok && a.TreeEntryBase.equalContent(b.TreeEntryBase) && a.Ref.Equals(b.Ref) } -type TreeEntrySymlink string +type TreeEntrySymlink struct { + TreeEntryBase + Target string +} func (tes TreeEntrySymlink) Type() TreeEntryType { return TETSymlink } func (tes TreeEntrySymlink) toProperties() properties { - return properties{"target": string(tes)} + props := tes.TreeEntryBase.toProperties() + props["target"] = tes.Target + return props } func (a TreeEntrySymlink) equalContent(_b TreeEntry) bool { b, ok := _b.(TreeEntrySymlink) - return ok && b == a + return ok && a.TreeEntryBase.equalContent(b.TreeEntryBase) && a.Target == b.Target } type Tree map[string]TreeEntry @@ -117,6 +165,28 @@ func getObjectIdFromProps(p properties, key string) (ObjectId, error) { return oid, err } +func defaultFileTreeEntryBase(_acl *acl.ACL, props properties) (base TreeEntryBase) { + base.user = props["user"] + base.group = props["group"] + if _acl == nil { + base.acl = acl.ACLFromUnixPerms(0664) + } else { + base.acl = *_acl + } + return +} + +func defaultDirTreeEntryBase(_acl *acl.ACL, props properties) (base TreeEntryBase) { + base.user = props["user"] + base.group = props["group"] + if _acl == nil { + base.acl = acl.ACLFromUnixPerms(0775) + } else { + base.acl = *_acl + } + return +} + func (t Tree) FromPayload(payload []byte) error { sc := bufio.NewScanner(bytes.NewReader(payload)) @@ -128,7 +198,16 @@ func (t Tree) FromPayload(payload []byte) error { props := make(properties) if err := props.UnmarshalText(line); err != nil { - return nil + return err + } + + var _acl *acl.ACL + if acl_string, ok := props["acl"]; ok { + acltmp, err := acl.ParseACL(acl_string) + if err != nil { + return err + } + _acl = &acltmp } entry_type, ok := props["type"] @@ -143,19 +222,28 @@ func (t Tree) FromPayload(payload []byte) error { if err != nil { return err } - entry = TreeEntryFile(ref) + entry = TreeEntryFile{ + TreeEntryBase: defaultFileTreeEntryBase(_acl, props), + Ref: ref, + } case TETDir: ref, err := getObjectIdFromProps(props, "ref") if err != nil { return err } - entry = TreeEntryDir(ref) + entry = TreeEntryDir{ + TreeEntryBase: defaultDirTreeEntryBase(_acl, props), + Ref: ref, + } case TETSymlink: target, ok := props["target"] if !ok { return errors.New("Missing key: target") } - entry = TreeEntrySymlink(target) + entry = TreeEntrySymlink{ + TreeEntryBase: defaultFileTreeEntryBase(_acl, props), + Target: target, + } default: // TODO: Or should we just ignore this entry? There might be more types in the future... return fmt.Errorf("Unknown tree entry type: %s", entry_type) diff --git a/objects/object_tree_test.go b/objects/object_tree_test.go index c80c2e1..3ec9e25 100644 --- a/objects/object_tree_test.go +++ b/objects/object_tree_test.go @@ -2,22 +2,65 @@ package objects import ( "bytes" + "code.laria.me/petrific/acl" "testing" ) var ( testTreeObj = Tree{ - "foo": TreeEntryFile(genId(0x11)), - "bar": TreeEntryDir(genId(0x22)), - "baz": TreeEntrySymlink("/föö&bär/💾"), // Test special chars and unicode - "😃": TreeEntryFile(genId(0x33)), + "foo": TreeEntryFile{ + TreeEntryBase: TreeEntryBase{ + acl: acl.ACLFromUnixPerms(0644), + user: "user1", + group: "group1", + }, + Ref: genId(0x11), + }, + "bar": TreeEntryDir{ + TreeEntryBase: TreeEntryBase{ + acl: acl.ACLFromUnixPerms(0755), + user: "user2", + group: "group2", + }, + Ref: genId(0x22), + }, + "baz": TreeEntrySymlink{ + TreeEntryBase: TreeEntryBase{ + acl: acl.ACLFromUnixPerms(0644), + user: "user3", + group: "group3", + }, + Target: "/föö&bär/💾", + }, // Test special chars and unicode + "😃": TreeEntryFile{ + TreeEntryBase: TreeEntryBase{ + acl: acl.ACL{ + User: acl.QualifiedPerms{ + "": acl.PermR | acl.PermW, + "user1": acl.PermR | acl.PermW, + }, + Group: acl.QualifiedPerms{ + "": acl.PermR, + }, + Other: acl.QualifiedPerms{ + "": acl.PermR, + }, + Mask: acl.QualifiedPerms{ + "": acl.PermR | acl.PermW, + }, + }, + user: "user4", + group: "group4", + }, + Ref: genId(0x33), + }, } testTreeSerialization = []byte("" + - "name=%f0%9f%98%83&ref=sha3-256:3333333333333333333333333333333333333333333333333333333333333333&type=file\n" + - "name=bar&ref=sha3-256:2222222222222222222222222222222222222222222222222222222222222222&type=dir\n" + - "name=baz&target=%2ff%c3%b6%c3%b6%26b%c3%a4r%2f%f0%9f%92%be&type=symlink\n" + - "name=foo&ref=sha3-256:1111111111111111111111111111111111111111111111111111111111111111&type=file\n") + "acl=u::rw-,g::r--,o::r--&group=group1&name=foo&ref=sha3-256:1111111111111111111111111111111111111111111111111111111111111111&type=file&user=user1\n" + + "acl=u::rw-,g::r--,o::r--&group=group3&name=baz&target=%2ff%c3%b6%c3%b6%26b%c3%a4r%2f%f0%9f%92%be&type=symlink&user=user3\n" + + "acl=u::rw-,u:user1:rw-,g::r--,o::r--,m::rw-&group=group4&name=%f0%9f%98%83&ref=sha3-256:3333333333333333333333333333333333333333333333333333333333333333&type=file&user=user4\n" + + "acl=u::rwx,g::r-x,o::r-x&group=group2&name=bar&ref=sha3-256:2222222222222222222222222222222222222222222222222222222222222222&type=dir&user=user2\n") ) func TestSerializeTree(t *testing.T) { diff --git a/objects/properties.go b/objects/properties.go index 6b4acc1..8c92932 100644 --- a/objects/properties.go +++ b/objects/properties.go @@ -11,14 +11,14 @@ import ( // (only the characters [a-zA-Z0-9.:_-] are allowed, values are ordered by their key) type properties map[string]string -// escapePropertyString escapes all bytes not in [a-zA-Z0-9.:_-] as %XX, where XX represents the hexadecimal value of the byte. +// escapePropertyString escapes all bytes not in [a-zA-Z0-9.,:_-] as %XX, where XX represents the hexadecimal value of the byte. // Compatible with URL query strings func escapePropertyString(s string) []byte { out := []byte{} esc := []byte("%XX") for _, b := range []byte(s) { - if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '.' || b == ':' || b == '_' || b == '-' { + if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b == '.' || b == ',' || b == ':' || b == '_' || b == '-' { out = append(out, b) } else { hex.Encode(esc[1:], []byte{b}) -- cgit v1.2.3-54-g00ecf