github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/model/permission/doctype.go (about)

     1  package permission
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strings"
     7  	"unicode"
     8  
     9  	"github.com/cozy/cozy-stack/pkg/consts"
    10  	"github.com/labstack/echo/v4"
    11  )
    12  
    13  var readable = true
    14  var none = false
    15  
    16  var blockList = map[string]bool{
    17  	// Global databases
    18  	consts.Instances:             none,
    19  	consts.AccountTypes:          none,
    20  	consts.KonnectorsMaintenance: none,
    21  	consts.RemoteSecrets:         none,
    22  
    23  	// Only stack can manipulate them
    24  	consts.Sessions:            none,
    25  	consts.Permissions:         none,
    26  	consts.Intents:             none,
    27  	consts.OAuthClients:        none,
    28  	consts.OAuthAccessCodes:    none,
    29  	consts.Archives:            none,
    30  	consts.Sharings:            none,
    31  	consts.Shared:              none,
    32  	consts.SoftDeletedAccounts: none,
    33  
    34  	// Synthetic doctypes (API only)
    35  	consts.CertifiedCarbonCopy:     none,
    36  	consts.CertifiedElectronicSafe: none,
    37  	consts.DirSizes:                none,
    38  	consts.TriggersState:           none,
    39  	consts.SharingsAnswer:          none,
    40  	consts.SharingsMoved:           none,
    41  	consts.Support:                 none,
    42  	consts.BitwardenProfiles:       none,
    43  	consts.OfficeURL:               none,
    44  	consts.NotesURL:                none,
    45  	consts.AppsOpenParameters:      none,
    46  
    47  	// Synthetic doctypes (realtime events only)
    48  	consts.AuthConfirmations:   none,
    49  	consts.JobEvents:           none,
    50  	consts.SharingsInitialSync: none,
    51  	consts.NotesEvents:         none,
    52  	consts.NotesTelepointers:   none,
    53  	consts.Thumbnails:          none,
    54  	consts.AppLogs:             none,
    55  
    56  	// Only stack can write them
    57  	consts.Jobs:              readable,
    58  	consts.Triggers:          readable,
    59  	consts.Apps:              readable,
    60  	consts.Konnectors:        readable,
    61  	consts.Files:             readable,
    62  	consts.FilesVersions:     readable,
    63  	consts.Notifications:     readable,
    64  	consts.RemoteRequests:    readable,
    65  	consts.SessionsLogins:    readable,
    66  	consts.NotesSteps:        readable,
    67  	consts.NotesImages:       readable,
    68  	consts.BitwardenContacts: readable,
    69  }
    70  
    71  // CheckReadable will abort the context and returns false if the doctype
    72  // is unreadable
    73  func CheckReadable(doctype string) error {
    74  	if err := CheckDoctypeName(doctype, false); err != nil {
    75  		return err
    76  	}
    77  
    78  	readable, inblocklist := blockList[doctype]
    79  	if !inblocklist || readable {
    80  		return nil
    81  	}
    82  
    83  	return &echo.HTTPError{
    84  		Code:    http.StatusForbidden,
    85  		Message: fmt.Sprintf("reserved doctype %s unreadable", doctype),
    86  	}
    87  }
    88  
    89  // CheckWritable will abort the echo context if the doctype
    90  // is unwritable
    91  func CheckWritable(doctype string) error {
    92  	if err := CheckDoctypeName(doctype, false); err != nil {
    93  		return err
    94  	}
    95  
    96  	_, inblocklist := blockList[doctype]
    97  	if !inblocklist {
    98  		return nil
    99  	}
   100  
   101  	return &echo.HTTPError{
   102  		Code:    http.StatusForbidden,
   103  		Message: fmt.Sprintf("reserved doctype %s unwritable", doctype),
   104  	}
   105  }
   106  
   107  // CheckDoctypeName will return an error if the doctype name is invalid.
   108  // A doctype name must be composed of lowercase letters, digits, . and _
   109  // characters to be valid.
   110  func CheckDoctypeName(doctype string, authorizeWildcard bool) error {
   111  	err := &echo.HTTPError{
   112  		Code:    http.StatusForbidden,
   113  		Message: fmt.Sprintf("%s is not a valid doctype name", doctype),
   114  	}
   115  
   116  	if len(doctype) == 0 {
   117  		return err
   118  	}
   119  
   120  	if authorizeWildcard && isWildcard(doctype) {
   121  		// Wildcards on too large domains are not allowed
   122  		if strings.Count(doctype, ".") < 3 {
   123  			return err
   124  		}
   125  		doctype = TrimWildcard(doctype)
   126  	}
   127  
   128  	for _, c := range doctype {
   129  		if unicode.IsLower(c) || unicode.IsDigit(c) || c == '.' || c == '_' {
   130  			continue
   131  		}
   132  		return err
   133  	}
   134  
   135  	// A dot at the beginning or the end of the doctype name is not allowed
   136  	if doctype[0] == '.' || doctype[len(doctype)-1] == '.' {
   137  		return err
   138  	}
   139  	// Two dots side-by-side are not allowed
   140  	if strings.Contains(doctype, "..") {
   141  		return err
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  const allDocTypes = "*"
   148  const wildcardSuffix = ".*"
   149  
   150  func isMaximal(doctype string) bool {
   151  	return doctype == allDocTypes
   152  }
   153  
   154  func isWildcard(doctype string) bool {
   155  	return strings.HasSuffix(doctype, wildcardSuffix)
   156  }
   157  
   158  // TrimWildcard returns the given doctype without the wildcard suffix
   159  func TrimWildcard(doctype string) string {
   160  	return strings.TrimSuffix(doctype, wildcardSuffix)
   161  }