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  }