github.com/cozy/cozy-stack@v0.0.0-20240327093429-939e4a21320e/model/permission/set.go (about) 1 package permission 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "io" 8 "reflect" 9 "strconv" 10 "strings" 11 ) 12 13 // Set is a Set of rule 14 type Set []Rule 15 16 // MarshalScopeString transforms a Set into a string for Oauth Scope 17 // (a space separated concatenation of its rules) 18 func (s Set) MarshalScopeString() (string, error) { 19 out := "" 20 if len(s) == 0 { 21 return "", nil 22 } 23 for _, r := range s { 24 scope, err := r.MarshalScopeString() 25 if err != nil { 26 return "", err 27 } 28 out += " " + scope 29 } 30 return out[1:], nil 31 } 32 33 // UnmarshalScopeString parse a Scope string into a permission Set 34 func UnmarshalScopeString(in string) (Set, error) { 35 if in == "" { 36 return nil, ErrBadScope 37 } 38 39 parts := strings.Split(in, ruleSep) 40 out := make(Set, len(parts)) 41 42 for i, p := range parts { 43 s, err := UnmarshalRuleString(p) 44 if err != nil { 45 return nil, err 46 } 47 out[i] = s 48 } 49 50 return out, nil 51 } 52 53 // MarshalJSON implements json.Marshaller on Set. Note that the JSON 54 // representation is a key-value object, but the golang Set is an ordered 55 // slice. In theory, JSON objects have no order on their keys, but here, we try 56 // to keep the same order on decoding/encoding. 57 // See docs/permissions.md for more details on the structure. 58 func (s Set) MarshalJSON() ([]byte, error) { 59 if len(s) == 0 { 60 return []byte("{}"), nil 61 } 62 buf := make([]byte, 0, 4096) 63 64 for i, r := range s { 65 title := r.Title 66 if title == "" { 67 title = "rule" + strconv.Itoa(i) 68 } 69 key, err := json.Marshal(title) 70 if err != nil { 71 return nil, err 72 } 73 val, err := json.Marshal(r) 74 if err != nil { 75 return nil, err 76 } 77 buf = append(buf, ',') 78 buf = append(buf, key...) 79 buf = append(buf, ':') 80 buf = append(buf, val...) 81 } 82 83 buf[0] = '{' 84 buf = append(buf, '}') 85 return buf, nil 86 } 87 88 // UnmarshalJSON parses a json formated permission set 89 func (s *Set) UnmarshalJSON(j []byte) error { 90 var raws map[string]json.RawMessage 91 err := json.Unmarshal(j, &raws) 92 if err != nil { 93 return err 94 } 95 titles, err := extractJSONKeys(j) 96 if err != nil { 97 return err 98 } 99 100 *s = make(Set, 0) 101 for _, title := range titles { 102 raw := raws[title] 103 var r Rule 104 err := json.Unmarshal(raw, &r) 105 if err != nil { 106 return err 107 } 108 r.Title = title 109 *s = append(*s, r) 110 } 111 return nil 112 } 113 114 func extractJSONKeys(j []byte) ([]string, error) { 115 var keys []string 116 dec := json.NewDecoder(bytes.NewReader(j)) 117 depth := 0 118 for { 119 t, err := dec.Token() 120 if errors.Is(err, io.EOF) { 121 break 122 } 123 if err != nil { 124 return nil, err 125 } 126 if t == json.Delim('{') { 127 depth++ 128 } else if t == json.Delim('}') { 129 depth-- 130 } else if depth == 1 { 131 if k, ok := t.(string); ok { 132 keys = append(keys, k) 133 } 134 } 135 } 136 return keys, nil 137 } 138 139 // Some returns true if the predicate return true for any of the rule. 140 func (s Set) Some(predicate func(Rule) bool) bool { 141 for _, r := range s { 142 if predicate(r) { 143 return true 144 } 145 } 146 return false 147 } 148 149 // RuleInSubset returns true if any document allowed by the rule 150 // is allowed by the set. 151 func (s *Set) RuleInSubset(r2 Rule) bool { 152 if s.IsMaximal() { 153 return true 154 } 155 156 for _, r := range *s { 157 if !MatchType(r, r2.Type) { 158 continue 159 } 160 161 if !r.Verbs.ContainsAll(r2.Verbs) { 162 continue 163 } 164 165 if r.Selector == "" && len(r.Values) == 0 { 166 return true 167 } 168 169 if r.Selector != r2.Selector { 170 continue 171 } 172 173 if r.ValuesContain(r2.Values...) { 174 return true 175 } 176 } 177 178 return false 179 } 180 181 // IsSubSetOf returns true if any document allowed by the set 182 // would have been allowed by parent. 183 func (s *Set) IsSubSetOf(parent Set) bool { 184 if s.IsMaximal() { 185 return false 186 } 187 if parent.IsMaximal() { 188 return true 189 } 190 for _, r := range *s { 191 if !parent.RuleInSubset(r) { 192 return false 193 } 194 } 195 196 return true 197 } 198 199 // HasSameRules returns true if the two sets have exactly the same rules. 200 func (s Set) HasSameRules(other Set) bool { 201 if len(s) != len(other) { 202 return false 203 } 204 205 for _, rule := range s { 206 match := false 207 for _, otherRule := range other { 208 if reflect.DeepEqual(rule.Values, otherRule.Values) && 209 rule.Selector == otherRule.Selector && 210 rule.Verbs.ContainsAll(otherRule.Verbs) && 211 otherRule.Verbs.ContainsAll(rule.Verbs) && 212 reflect.DeepEqual(otherRule.Type, rule.Type) { 213 match = true 214 break 215 } 216 } 217 218 if !match { 219 return false 220 } 221 } 222 223 return true 224 } 225 226 // Diff returns a the differences between two sets. 227 // Useful to see what rules had been added between a original manifest 228 // permissions and now. 229 // 230 // We are ignoring removed values/verbs between rule 1 and rule 2. 231 // - At the moment, it onlys show the added values, verbs and rules 232 func Diff(set1, set2 Set) Set { 233 // If sets are the same, do not compute 234 if set1.HasSameRules(set2) { 235 return set1 236 } 237 238 newSet := Set{} 239 240 // Appending not existing rules 241 for _, r2 := range set2 { 242 found := false 243 244 for _, r := range set1 { 245 if r.Title == r2.Title { 246 // Rule exist 247 found = true 248 break 249 } 250 } 251 252 if !found { 253 newSet = append(newSet, r2) 254 } 255 } 256 257 // Compare each key 258 for _, rule1 := range set1 { 259 for _, rule2 := range set2 { 260 if rule1.Title == rule2.Title { // Same rule, we are going to compute differences 261 newRule := Rule{ 262 Title: rule1.Title, 263 Type: rule1.Type, 264 Verbs: map[Verb]struct{}{}, 265 Values: []string{}, 266 } 267 268 // Handle verbs. Here we are going to find verbs in set2 that 269 // are not present in set1, meaning they were added later by an 270 // external human action 271 for verb2, content2 := range rule2.Verbs { 272 if !rule1.Verbs.Contains(verb2) { 273 newRule.Verbs[verb2] = content2 274 } 275 } 276 277 // Handle values 278 for _, value2 := range rule2.Values { 279 if ok := rule1.ValuesContain(value2); !ok { 280 newRule.Values = append(newRule.Values, value2) 281 } 282 } 283 284 newSet = append(newSet, newRule) 285 } 286 } 287 } 288 return newSet 289 } 290 291 // IsMaximal returns true if the permission is valid for everything. Only the 292 // flagship app should have it, as it is really powerful. 293 func (s *Set) IsMaximal() bool { 294 return s.Some(func(r Rule) bool { 295 return isMaximal(r.Type) 296 }) 297 } 298 299 // MaximalSet returns the maximal permission, for the flagship app. It gives 300 // access to every endpoint. 301 func MaximalSet() Set { 302 return Set{ 303 Rule{Type: allDocTypes}, 304 } 305 }