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  }