user API: allow to disable writes ...

... even if the user has permissions for these actions
This commit is contained in:
Nicola Murino 2021-07-23 21:41:02 +02:00
parent 85a47810ff
commit 83c7453957
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
7 changed files with 97 additions and 13 deletions

View file

@ -181,6 +181,7 @@ func uploadUserFiles(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "Unable to parse multipart form", http.StatusBadRequest)
return
}
defer r.MultipartForm.RemoveAll() //nolint:errcheck
parentDir := util.CleanPath(r.URL.Query().Get("path"))
files := r.MultipartForm.File["filename"]

View file

@ -373,7 +373,8 @@ func TestBasicUserHandling(t *testing.T) {
user.ExpirationDate = util.GetTimeAsMsSinceEpoch(time.Now())
user.AdditionalInfo = "some free text"
user.Filters.TLSUsername = sdk.TLSUsernameCN
user.Filters.WebClient = append(user.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled)
user.Filters.WebClient = append(user.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled,
sdk.WebClientWriteDisabled)
originalUser := user
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
@ -6352,6 +6353,86 @@ func TestWebAPIVFolder(t *testing.T) {
assert.NoError(t, err)
}
func TestWebAPIWritePermission(t *testing.T) {
u := getTestUser()
u.Filters.WebClient = append(u.Filters.WebClient, sdk.WebClientWriteDisabled)
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err)
webAPIToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
body := new(bytes.Buffer)
writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("filename", "file.txt")
assert.NoError(t, err)
_, err = part.Write([]byte(""))
assert.NoError(t, err)
err = writer.Close()
assert.NoError(t, err)
reader := bytes.NewReader(body.Bytes())
req, err := http.NewRequest(http.MethodPost, userFilePath, reader)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr := executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
req, err = http.NewRequest(http.MethodPatch, userFilePath+"?path=a&target=b", nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
req, err = http.NewRequest(http.MethodDelete, userFilePath+"?path=a", nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
req, err = http.NewRequest(http.MethodGet, userFilePath+"?path=a.txt", nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, rr)
req, err = http.NewRequest(http.MethodGet, userFolderPath, nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
req, err = http.NewRequest(http.MethodPost, userFolderPath+"?path=dir", nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
req, err = http.NewRequest(http.MethodPatch, userFolderPath+"?path=dir&target=dir1", nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
req, err = http.NewRequest(http.MethodDelete, userFolderPath+"?path=dir", nil)
assert.NoError(t, err)
req.Header.Add("Content-Type", writer.FormDataContentType())
setBearerForReq(req, webAPIToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestWebAPICryptFs(t *testing.T) {
u := getTestUser()
u.QuotaSize = 65535

View file

@ -123,7 +123,6 @@ func jwtAuthenticatorWebClient(next http.Handler) http.Handler {
})
}
//nolint:unparam
func checkHTTPUserPerm(perm string) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

View file

@ -2228,9 +2228,11 @@ components:
type: string
enum:
- publickey-change-disabled
- write-disabled
description: |
Options:
* `publickey-change-disabled` - changing SSH public keys is not allowed
* `write-disabled` - upload, rename, delete are not allowed even if the user has permissions for these actions
PatternsFilter:
type: object
properties:
@ -2321,7 +2323,7 @@ components:
type: array
items:
$ref: '#/components/schemas/WebClientOptions'
description: WebClient related configuration options
description: WebClient/user REST API related configuration options
description: Additional user options
Secret:
type: object

View file

@ -632,13 +632,13 @@ func (s *httpdServer) initializeRouter() {
router.With(checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Get(userPublicKeysPath, getUserPublicKeys)
router.With(checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).Put(userPublicKeysPath, setUserPublicKeys)
router.Get(userFolderPath, readUserFolder)
router.Post(userFolderPath, createUserDir)
router.Patch(userFolderPath, renameUserDir)
router.Delete(userFolderPath, deleteUserDir)
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Post(userFolderPath, createUserDir)
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Patch(userFolderPath, renameUserDir)
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Delete(userFolderPath, deleteUserDir)
router.Get(userFilePath, getUserFile)
router.Post(userFilePath, uploadUserFiles)
router.Patch(userFilePath, renameUserFile)
router.Delete(userFilePath, deleteUserFile)
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Post(userFilePath, uploadUserFiles)
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Patch(userFilePath, renameUserFile)
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled)).Delete(userFilePath, deleteUserFile)
router.Post(userStreamZipPath, getUserFilesAsZipStream)
})

View file

@ -6,14 +6,15 @@ import (
"github.com/drakkan/sftpgo/v2/util"
)
// Web Client restrictions
// Web Client/user REST API restrictions
const (
WebClientPubKeyChangeDisabled = "publickey-change-disabled"
WebClientWriteDisabled = "write-disabled"
)
var (
// WebClientOptions defines the available options for the web client interface
WebClientOptions = []string{WebClientPubKeyChangeDisabled}
// WebClientOptions defines the available options for the web client interface/user REST API
WebClientOptions = []string{WebClientPubKeyChangeDisabled, WebClientWriteDisabled}
)
// TLSUsername defines the TLS certificate attribute to use as username

View file

@ -538,7 +538,7 @@
</div>
<div class="form-group row">
<label for="idWebClient" class="col-sm-2 col-form-label">Web client</label>
<label for="idWebClient" class="col-sm-2 col-form-label">Web client/REST API</label>
<div class="col-sm-10">
<select class="form-control" id="idWebClient" name="web_client_options" multiple>
{{range $option := .WebClientOptions}}