test(files): add permission tests for basic file operations, with some security fixes

This commit is contained in:
hexxa 2022-02-21 17:38:20 +08:00 committed by Hexxa
parent 980bceb090
commit cff87bdddd
4 changed files with 462 additions and 71 deletions

View file

@ -24,7 +24,7 @@ import (
"github.com/ihexxa/quickshare/src/worker/localworker"
)
var (
const (
// queries
FilePathQuery = "fp"
ListDirQuery = "dp"
@ -104,7 +104,7 @@ func (h *FileHandlers) canAccess(userName, role, op, sharedPath string) bool {
parts := strings.Split(sharedPath, "/")
if len(parts) < 2 { // the path must be longer than <userName>/files
return false
} else if parts[0] == userName {
} else if parts[0] == userName && userName != "" && parts[1] != "" {
return true
}
@ -143,6 +143,12 @@ func (h *FileHandlers) Create(c *gin.Context) {
return
}
fsFilePath, err := h.getFSFilePath(userID, req.Path)
if err != nil {
c.JSON(q.ErrResp(c, 500, err))
return
}
if req.FileSize == 0 {
err = h.deps.FS().MkdirAll(filepath.Dir(req.Path))
if err != nil {
@ -152,12 +158,6 @@ func (h *FileHandlers) Create(c *gin.Context) {
// TODO: limit the number of files with 0 byte
fsFilePath, err := h.getFSFilePath(userID, req.Path)
if err != nil {
c.JSON(q.ErrResp(c, 500, err))
return
}
err = h.deps.FS().Create(fsFilePath)
if err != nil {
if os.IsExist(err) {
@ -512,6 +512,7 @@ func (h *FileHandlers) getFSFilePath(userID, fsFilePath string) (string, error)
// this file exists
maxDetect := 1024
fsFilePath = filepath.Clean(fsFilePath)
for i := 1; i < maxDetect; i++ {
dir := path.Dir(fsFilePath)
nameAndExt := path.Base(fsFilePath)
@ -1041,11 +1042,10 @@ func (h *FileHandlers) GetSharingDir(c *gin.Context) {
func (h *FileHandlers) GetStreamReader(userID uint64, fd io.Reader) (io.ReadCloser, error) {
pr, pw := io.Pipe()
chunkSize := 100 * 1024 // notice: it can not be greater than limiter's token count
go func() {
for {
ok, err := h.deps.Limiter().CanRead(userID, chunkSize)
ok, err := h.deps.Limiter().CanRead(userID, q.DownloadChunkSize)
if err != nil {
pw.CloseWithError(err)
break
@ -1054,7 +1054,7 @@ func (h *FileHandlers) GetStreamReader(userID uint64, fd io.Reader) (io.ReadClos
continue
}
_, err = io.CopyN(pw, fd, int64(chunkSize))
_, err = io.CopyN(pw, fd, int64(q.DownloadChunkSize))
if err != nil {
if err != io.EOF {
pw.CloseWithError(err)

View file

@ -156,6 +156,9 @@ func (h *MultiUsersSvc) Init(adminName, adminPwd string) (string, error) {
spaceLimit := int64(h.cfg.IntOr("Users.SpaceLimit", 100*1024*1024))
uploadSpeedLimit := h.cfg.IntOr("Users.UploadSpeedLimit", 100*1024)
downloadSpeedLimit := h.cfg.IntOr("Users.DownloadSpeedLimit", 100*1024)
if downloadSpeedLimit < q.DownloadChunkSize {
return "", fmt.Errorf("download speed limit can not be lower than chunk size: %d", q.DownloadChunkSize)
}
if ok {
userCfgs, ok := usersInterface.([]*userstore.UserCfg)
if !ok {
@ -166,7 +169,6 @@ func (h *MultiUsersSvc) Init(adminName, adminPwd string) (string, error) {
// TODO: check if the folders already exists
fsRootFolder := q.FsRootPath(userCfg.Name, "/")
if err = h.deps.FS().MkdirAll(fsRootFolder); err != nil {
return "", err
}
uploadFolder := q.UploadFolder(userCfg.Name)
@ -276,7 +278,7 @@ func (h *MultiUsersSvc) IsAuthed(c *gin.Context) {
// token alreay verified in the authn middleware
role := c.MustGet(q.RoleParam).(string)
if role == userstore.VisitorRole {
c.JSON(q.ErrResp(c, 401, q.ErrUnauthorized))
c.JSON(q.ErrResp(c, 403, q.ErrUnauthorized))
return
}
c.JSON(q.Resp(200))
@ -299,7 +301,7 @@ func (h *MultiUsersSvc) SetPwd(c *gin.Context) {
claims, err := h.getUserInfo(c)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
c.JSON(q.ErrResp(c, 403, err))
return
}
@ -350,7 +352,7 @@ func (h *MultiUsersSvc) ForceSetPwd(c *gin.Context) {
claims, err := h.getUserInfo(c)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
c.JSON(q.ErrResp(c, 403, err))
return
}
if claims[q.RoleParam] != userstore.AdminRole {
@ -472,7 +474,7 @@ func (h *MultiUsersSvc) DelUser(c *gin.Context) {
claims, err := h.getUserInfo(c)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
c.JSON(q.ErrResp(c, 403, err))
return
}
if claims[q.UserIDParam] == userIDStr {
@ -645,7 +647,7 @@ type SelfResp struct {
func (h *MultiUsersSvc) Self(c *gin.Context) {
claims, err := h.getUserInfo(c)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
c.JSON(q.ErrResp(c, 403, err))
return
}
@ -679,6 +681,12 @@ func (h *MultiUsersSvc) SetUser(c *gin.Context) {
return
}
role := c.MustGet(q.RoleParam).(string)
if role != userstore.AdminRole {
c.JSON(q.ErrResp(c, 403, errors.New("Forbidden")))
return
}
err := h.deps.Users().SetInfo(req.ID, &userstore.User{
Role: req.Role,
Quota: req.Quota,

View file

@ -27,6 +27,10 @@ var (
TokenCookie = "tk"
LastID = "lid"
// DownloadChunkSize can not be greater than limiter's token count
// downloadSpeedLimit can not be lower than DownloadChunkSize
DownloadChunkSize = 100 * 1024
ErrAccessDenied = errors.New("access denied")
ErrUnauthorized = errors.New("unauthorized")
)

View file

@ -2,19 +2,15 @@ package server
import (
"encoding/base64"
"fmt"
"math/rand"
"net/http"
"os"
"path/filepath"
"strconv"
"sync"
"testing"
"time"
"github.com/ihexxa/quickshare/src/client"
"github.com/ihexxa/quickshare/src/db/userstore"
q "github.com/ihexxa/quickshare/src/handlers"
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
)
func TestPermissions(t *testing.T) {
@ -37,11 +33,26 @@ func TestPermissions(t *testing.T) {
"pwd": "1234",
"role": "admin"
},
{
"name": "admin2",
"pwd": "1234",
"role": "admin"
},
{
"name": "user",
"pwd": "1234",
"role": "user"
},
{
"name": "user2",
"pwd": "1234",
"role": "user"
},
{
"name": "share",
"pwd": "1234",
"role": "user"
}
]
},
"server": {
@ -63,30 +74,15 @@ func TestPermissions(t *testing.T) {
srv := startTestServer(config)
defer srv.Shutdown()
fs := srv.depsFS()
if !isServerReady(addr) {
t.Fatal("fail to start server")
}
// adminUsersCl := client.NewSingleUserClient(addr)
// resp, _, errs := adminUsersCl.Login(adminName, adminPwd)
// if len(errs) > 0 {
// t.Fatal(errs)
// } else if resp.StatusCode != 200 {
// t.Fatal(resp.StatusCode)
// }
// adminToken := client.GetCookie(resp.Cookies(), q.TokenCookie)
// cl := client.NewFilesClient(addr, adminToken)
var err error
// TODO: remove all files under home folder before testing
// or the count of files is incorrect
// tests only check the status code for checking permission
t.Run("Users API Permissions", func(t *testing.T) {
testUsersAPIs := func(user string, pwd string, requireAuth bool, expectedCodes map[string]int) {
cl := client.NewSingleUserClient(addr)
var token *http.Cookie
token := &http.Cookie{}
if requireAuth {
resp, _, errs := cl.Login(user, pwd)
if len(errs) > 0 {
@ -135,7 +131,7 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
// test user operations
@ -145,7 +141,7 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
expectedCode = expectedCodes["ListUsers"]
@ -153,21 +149,53 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
// TODO: the id here should be uint64
uintID, err := strconv.ParseUint(addUserResp.ID, 64, 10)
if err != nil {
t.Fatal(err)
tmpUserID := uint64(0)
var err error
if addUserResp.ID != "" {
tmpUserID, err = strconv.ParseUint(addUserResp.ID, 10, 64)
if err != nil {
t.Fatal(err)
}
}
newRole := "user"
expectedCode = expectedCodes["SetUser"]
resp, _, errs = cl.SetUser(uintID, newRole, selfResp.Quota, token)
userID := uint64(0)
if selfResp.ID != "" {
userID, err = strconv.ParseUint(selfResp.ID, 10, 64)
if err != nil {
t.Fatal(err)
}
}
newRole := userstore.AdminRole
newQuota := &userstore.Quota{
SpaceLimit: int64(2046),
UploadSpeedLimit: int(8 * 1024 * 1024),
DownloadSpeedLimit: int(8 * 1024 * 1024),
}
// update self
expectedCode = expectedCodes["SetUserSelf"]
resp, _, errs = cl.SetUser(userID, newRole, newQuota, token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
// update other users
expectedCode = expectedCodes["SetUserOthers"]
resp, _, errs = cl.SetUser(tmpUserID, userstore.AdminRole, newQuota, token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
resp, _, errs = cl.SetUser(0, userstore.UserRole, newQuota, token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
expectedCode = expectedCodes["DelUser"]
@ -175,7 +203,7 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
// test role operations
@ -185,7 +213,7 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
expectedCode = expectedCodes["ListRoles"]
@ -193,7 +221,7 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
expectedCode = expectedCodes["DelRole"]
@ -201,7 +229,7 @@ func TestPermissions(t *testing.T) {
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatalf("%s %d", user, resp.StatusCode)
t.Fatalf("%s %d %d", user, expectedCode, resp.StatusCode)
}
if requireAuth {
@ -221,7 +249,9 @@ func TestPermissions(t *testing.T) {
"IsAuthed": 200,
"AddUser": 200,
"ListUsers": 200,
"SetUser": 200,
"SetUserSelf": 200,
"SetUserOthers": 200,
"SetOtherUser": 200,
"DelUser": 200,
"AddRole": 200,
"ListRoles": 200,
@ -233,27 +263,376 @@ func TestPermissions(t *testing.T) {
"Self": 200,
"SetPreferences": 200,
"IsAuthed": 200,
"AddUser": 401,
"ListUsers": 401,
"SetUser": 401,
"DelUser": 401,
"AddRole": 401,
"ListRoles": 401,
"DelRole": 401,
"AddUser": 403,
"ListUsers": 403,
"SetUserSelf": 403,
"SetUserOthers": 403,
"DelUser": 403,
"AddRole": 403,
"ListRoles": 403,
"DelRole": 403,
})
testUsersAPIs("visitor", "", false, map[string]int{
"SetPwd": 401,
"Self": 401,
"SetPreferences": 401,
"IsAuthed": 401,
"AddUser": 401,
"ListUsers": 401,
"SetUser": 401,
"DelUser": 401,
"AddRole": 401,
"ListRoles": 401,
"DelRole": 401,
"SetPwd": 403,
"Self": 403,
"SetPreferences": 403,
"IsAuthed": 403,
"AddUser": 403,
"ListUsers": 403,
"SetUserSelf": 403,
"SetUserOthers": 403,
"DelUser": 403,
"AddRole": 403,
"ListRoles": 403,
"DelRole": 403,
})
})
t.Run("Files operation API Permissions", func(t *testing.T) {
// ListUploadings(c *gin.Context) {
// DelUploading(c *gin.Context) {
// AddSharing(c *gin.Context) {
// DelSharing(c *gin.Context) {
// IsSharing(c *gin.Context) {
// ListSharings(c *gin.Context) {
// ListSharingIDs(c *gin.Context) {
// GenerateHash(c *gin.Context) {
// GetSharingDir(c *gin.Context) {
testFolderOpPermission := func(user string, pwd string, requireAuth bool, expectedCodes map[string]int) {
// List(c *gin.Context) {
// ListHome(c *gin.Context) {
// Mkdir(c *gin.Context) {
// Move(c *gin.Context) {
// Create(c *gin.Context) {
// UploadChunk(c *gin.Context) {
// UploadStatus(c *gin.Context) {
// Metadata(c *gin.Context) {
// Move(c *gin.Context) {
// Download(c *gin.Context) {
// GetStreamReader(userID uint64, fd io.Reader) (io.ReadCloser, error) {
// Delete(c *gin.Context) {
cl := client.NewSingleUserClient(addr)
token := &http.Cookie{}
if requireAuth {
resp, _, errs := cl.Login(user, pwd)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
token = client.GetCookie(resp.Cookies(), q.TokenCookie)
}
filesCl := client.NewFilesClient(addr, token)
expectedCode := expectedCodes["ListHome"]
resp, lhResp, errs := filesCl.ListHome()
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["List"]
homePath := lhResp.Cwd
if !requireAuth {
homePath = "/"
}
resp, _, errs = filesCl.List(homePath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["ListPaths"]
for _, itemPath := range []string{
"/",
"admin/",
"admin/files",
"user2/",
"user2/files",
} {
resp, _, errs = filesCl.List(itemPath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
}
expectedCode = expectedCodes["Mkdir"]
testPath := filepath.Join(lhResp.Cwd, "test")
resp, _, errs = filesCl.Mkdir(testPath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["Move"]
newPath := filepath.Join(lhResp.Cwd, "test2")
resp, _, errs = filesCl.Move(testPath, newPath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
if requireAuth {
resp, _, errs := cl.Logout(token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(user, resp.StatusCode, expectedCode)
}
}
}
testFolderOpPermission("admin", "1234", true, map[string]int{
"ListHome": 200,
"List": 200,
"ListPaths": 200,
"Mkdir": 200,
"Move": 200,
})
testFolderOpPermission("user", "1234", true, map[string]int{
"ListHome": 200,
"List": 200,
"ListPaths": 403,
"Mkdir": 200,
"Move": 200,
})
testFolderOpPermission("visitor", "", false, map[string]int{
"ListHome": 403,
"List": 403,
"ListPaths": 403,
"Mkdir": 403,
"Move": 403,
})
testFileOpPermission := func(user string, pwd string, requireAuth bool, targetPath, targetFile string, expectedCodes map[string]int) {
// Create(c *gin.Context) {
// UploadChunk(c *gin.Context) {
// UploadStatus(c *gin.Context) {
// Metadata(c *gin.Context) {
// Move(c *gin.Context) {
// Download(c *gin.Context) {
// Delete(c *gin.Context) {
cl := client.NewSingleUserClient(addr)
token := &http.Cookie{}
if requireAuth {
resp, _, errs := cl.Login(user, pwd)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
token = client.GetCookie(resp.Cookies(), q.TokenCookie)
}
expectedCode := expectedCodes["ListHome"]
filesCl := client.NewFilesClient(addr, token)
resp, _, errs := filesCl.ListHome()
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
fileContent := []byte("01010")
filePath := filepath.Join(targetPath, "old")
fileSize := int64(len(fileContent))
expectedCode = expectedCodes["Create"]
resp, _, errs = filesCl.Create(filePath, fileSize)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["UploadStatus"]
resp, _, errs = filesCl.UploadStatus(filePath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["UploadChunk"]
base64Content := base64.StdEncoding.EncodeToString([]byte(fileContent))
resp, _, errs = filesCl.UploadChunk(filePath, base64Content, 0)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["Metadata"]
resp, _, errs = filesCl.Metadata(filePath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["MetadataTarget"]
resp, _, errs = filesCl.Metadata(targetPath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["Download"]
resp, _, errs = filesCl.Download(filePath, map[string]string{})
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
if targetFile != "" {
expectedCode = expectedCodes["DownloadTarget"]
resp, _, errs = filesCl.Download(targetFile, map[string]string{})
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
}
expectedCode = expectedCodes["Move"]
newPath := filepath.Join(targetPath, "new")
resp, _, errs = filesCl.Move(filePath, newPath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
expectedCode = expectedCodes["Delete"]
resp, _, errs = filesCl.Delete(newPath)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != expectedCode {
t.Fatal(user, resp.StatusCode, expectedCode)
}
if requireAuth {
resp, _, errs := cl.Logout(token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(user, resp.StatusCode, expectedCode)
}
}
}
testFileOpPermission("admin", "1234", true, "admin/files", "", map[string]int{
"ListHome": 200,
"Create": 200,
"UploadChunk": 200,
"UploadStatus": 200,
"Metadata": 200,
"MetadataTarget": 200,
"Move": 200,
"Download": 200,
"Delete": 200,
})
testFileOpPermission("user", "1234", true, "user/files", "", map[string]int{
"ListHome": 200,
"Create": 200,
"UploadChunk": 200,
"UploadStatus": 200,
"Metadata": 200,
"MetadataTarget": 200,
"Move": 200,
"Download": 200,
"Delete": 200,
})
testFileOpPermission("visitor", "", false, "user/files", "", map[string]int{
"ListHome": 403,
"Create": 403,
"UploadChunk": 403,
"UploadStatus": 403,
"Metadata": 403,
"MetadataTarget": 403,
"Move": 403,
"Download": 403,
"Delete": 403,
})
testFileOpPermission("admin", "1234", true, "user2/files", "", map[string]int{
"ListHome": 200,
"Create": 200,
"UploadChunk": 200,
"UploadStatus": 200,
"Metadata": 200,
"MetadataTarget": 200,
"Move": 200,
"Download": 200,
"Delete": 200,
})
testFileOpPermission("user", "1234", true, "user2/files", "", map[string]int{
"ListHome": 200,
"Create": 403,
"UploadChunk": 403,
"UploadStatus": 403,
"Metadata": 403,
"MetadataTarget": 403,
"Move": 403,
"Download": 403,
"Delete": 403,
})
// test sharing permission
enableSharing := func() {
cl := client.NewSingleUserClient(addr)
token := &http.Cookie{}
resp, _, errs := cl.Login("share", "1234")
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
token = client.GetCookie(resp.Cookies(), q.TokenCookie)
filesCl := client.NewFilesClient(addr, token)
resp, _, errs = filesCl.AddSharing("share/files")
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
assertUploadOK(t, "share/files/share", "101", addr, token)
}
enableSharing()
testFileOpPermission("user", "1234", true, "share/files", "share/files/share", map[string]int{
"ListHome": 200,
"Create": 403,
"UploadChunk": 403,
"UploadStatus": 403,
"Metadata": 403,
"MetadataTarget": 403,
"Move": 403,
"Download": 404,
"DownloadTarget": 200,
"Delete": 403,
// List is not tested
})
})
t.Run("Settings API Permissions", func(t *testing.T) {
})
}