github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/vfs/permissions.go (about) 1 package vfs 2 3 import ( 4 "errors" 5 "fmt" 6 "strings" 7 8 "github.com/cozy/cozy-stack/model/permission" 9 "github.com/cozy/cozy-stack/pkg/consts" 10 ) 11 12 // Fetcher extends on permission.Fetcher with hierarchy functions 13 type Fetcher interface { 14 permission.Fetcher 15 parentID() string 16 Path(fs FilePather) (string, error) 17 Parent(fs VFS) (*DirDoc, error) 18 } 19 20 // FileDoc & DirDoc are vfs.Fetcher 21 var _ Fetcher = (*FileDoc)(nil) 22 var _ Fetcher = (*DirDoc)(nil) 23 24 // Allows check if a permSet allows verb on given file 25 func Allows(fs VFS, pset permission.Set, v permission.Verb, fd Fetcher) error { 26 allowedIDs := []string{} 27 otherRules := []permission.Rule{} 28 29 // First pass, we iterate over the rules, check if we have an easy match 30 // keep a short list of useful rules and allowed IDs. 31 for _, r := range pset { 32 typ := permission.TrimWildcard(r.Type) 33 if typ != consts.Files && !strings.HasPrefix(consts.Files, typ+".") { 34 continue 35 } 36 if !r.Verbs.Contains(v) { 37 continue 38 } 39 40 // permission on whole io.cozy.files doctype 41 if len(r.Values) == 0 { 42 return nil 43 } 44 45 // permission by ID directly on self, parent or root 46 if r.Selector == "" { 47 for _, v := range r.Values { 48 if v == fd.ID() || v == fd.parentID() || v == consts.RootDirID { 49 return nil 50 } 51 allowedIDs = append(allowedIDs, v) 52 } 53 } 54 55 // permission by attributes values (tags, mime ...) on self 56 valid := func(value string) bool { 57 candidates := fd.Fetch(r.Selector) 58 for _, candidate := range candidates { 59 if value == candidate { 60 return true 61 } 62 } 63 return false 64 } 65 if r.SomeValue(valid) { 66 return nil 67 } 68 69 // store rules that could apply to an ancestor 70 if r.Selector != "mime" && r.Selector != "class" { 71 otherRules = append(otherRules, r) 72 } 73 } 74 75 // We have some rules on IDs, let's fetch their paths and check if they are 76 // ancestors of current object 77 if len(allowedIDs) > 0 { 78 selfPath, err := fd.Path(fs) 79 if err != nil { 80 return err 81 } 82 83 for _, id := range allowedIDs { 84 allowedPath, err := pathFromID(fs, id) 85 // tested is children of allowed ( err is ignored, it most probably 86 // means a permissions on a deleted directory) 87 if err == nil && strings.HasPrefix(selfPath, allowedPath+"/") { 88 return nil 89 } 90 } 91 } 92 93 // We have some rules on attributes, let's iterate over the current object 94 // ancestors and check if any match the rules 95 if len(otherRules) > 0 { 96 cur, err := fd.Parent(fs) 97 if err != nil { 98 return err 99 } 100 for cur.ID() != consts.RootDirID { 101 for _, rule := range otherRules { 102 if rule.ValuesMatch(cur) { 103 return nil 104 } 105 cur, err = cur.Parent(fs) 106 if err != nil { 107 return err 108 } 109 } 110 } 111 } 112 113 // no match : game over ! 114 return errors.New("no permission") 115 } 116 117 func pathFromID(fs VFS, id string) (string, error) { 118 if id == consts.RootDirID { 119 return "", nil 120 } 121 122 if id == consts.TrashDirID { 123 return TrashDirName, nil 124 } 125 126 dir, err := fs.DirByID(id) 127 if err != nil { 128 return "", err 129 } 130 131 return dir.Path(fs) 132 } 133 134 func (f *FileDoc) parentID() string { return f.DirID } 135 func (d *DirDoc) parentID() string { return d.DirID } 136 137 // Fetch implements permission.Fetch on FileDoc 138 func (f *FileDoc) Fetch(field string) []string { 139 switch field { 140 case "type": 141 return []string{f.Type} 142 case "name": 143 return []string{f.DocName} 144 case "mime": 145 return []string{f.Mime} 146 case "class": 147 return []string{f.Class} 148 case "tags": 149 return f.Tags 150 case "referenced_by": 151 if f != nil { 152 var values []string 153 for _, ref := range f.ReferencedBy { 154 // 2 formats are possible: 155 // - only the identifier 156 // - doctype/docid 157 values = append(values, ref.ID, fmt.Sprintf("%s/%s", ref.Type, ref.ID)) 158 } 159 return values 160 } 161 } 162 return nil 163 } 164 165 // Fetch implements permission.Fetcher on DirDOc 166 func (d *DirDoc) Fetch(field string) []string { 167 switch field { 168 case "type": 169 return []string{d.Type} 170 case "name": 171 return []string{d.DocName} 172 case "tags": 173 return d.Tags 174 case "referenced_by": 175 var values []string 176 for _, ref := range d.ReferencedBy { 177 values = append(values, ref.ID) 178 } 179 return values 180 } 181 return nil 182 }