123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- package httpd
- import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "time"
- "github.com/go-chi/render"
- "github.com/rs/xid"
- "github.com/drakkan/sftpgo/v2/common"
- "github.com/drakkan/sftpgo/v2/dataprovider"
- "github.com/drakkan/sftpgo/v2/util"
- )
- func readUserFolder(w http.ResponseWriter, r *http.Request) {
- claims, err := getTokenClaims(r)
- if err != nil || claims.Username == "" {
- sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
- return
- }
- user, err := dataprovider.UserExists(claims.Username)
- if err != nil {
- sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
- return
- }
- connID := xid.New().String()
- connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
- if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
- sendAPIResponse(w, r, err, http.StatusText(http.StatusForbidden), http.StatusForbidden)
- return
- }
- connection := &Connection{
- BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, r.RemoteAddr, user),
- request: r,
- }
- common.Connections.Add(connection)
- defer common.Connections.Remove(connection.GetID())
- name := util.CleanPath(r.URL.Query().Get("path"))
- contents, err := connection.ReadDir(name)
- if err != nil {
- sendAPIResponse(w, r, err, "Unable to get directory contents", getMappedStatusCode(err))
- return
- }
- results := make([]map[string]interface{}, 0, len(contents))
- for _, info := range contents {
- res := make(map[string]interface{})
- res["name"] = info.Name()
- if info.Mode().IsRegular() {
- res["size"] = info.Size()
- }
- res["mode"] = info.Mode()
- res["last_modified"] = info.ModTime().UTC().Format(time.RFC3339)
- results = append(results, res)
- }
- render.JSON(w, r, results)
- }
- func getUserFile(w http.ResponseWriter, r *http.Request) {
- claims, err := getTokenClaims(r)
- if err != nil || claims.Username == "" {
- sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
- return
- }
- user, err := dataprovider.UserExists(claims.Username)
- if err != nil {
- sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
- return
- }
- connID := xid.New().String()
- connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
- if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
- sendAPIResponse(w, r, err, http.StatusText(http.StatusForbidden), http.StatusForbidden)
- return
- }
- connection := &Connection{
- BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, r.RemoteAddr, user),
- request: r,
- }
- common.Connections.Add(connection)
- defer common.Connections.Remove(connection.GetID())
- name := util.CleanPath(r.URL.Query().Get("path"))
- if name == "/" {
- sendAPIResponse(w, r, nil, "Please set the path to a valid file", http.StatusBadRequest)
- return
- }
- info, err := connection.Stat(name, 0)
- if err != nil {
- sendAPIResponse(w, r, err, "Unable to stat the requested file", getMappedStatusCode(err))
- return
- }
- if info.IsDir() {
- sendAPIResponse(w, r, nil, fmt.Sprintf("Please set the path to a valid file, %#v is a directory", name), http.StatusBadRequest)
- return
- }
- if status, err := downloadFile(w, r, connection, name, info); err != nil {
- resp := apiResponse{
- Error: err.Error(),
- Message: http.StatusText(status),
- }
- ctx := r.Context()
- if status != 0 {
- ctx = context.WithValue(ctx, render.StatusCtxKey, status)
- }
- render.JSON(w, r.WithContext(ctx), resp)
- }
- }
- func getUserFilesAsZipStream(w http.ResponseWriter, r *http.Request) {
- claims, err := getTokenClaims(r)
- if err != nil || claims.Username == "" {
- sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
- return
- }
- user, err := dataprovider.UserExists(claims.Username)
- if err != nil {
- sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
- return
- }
- connID := xid.New().String()
- connectionID := fmt.Sprintf("%v_%v", common.ProtocolHTTP, connID)
- if err := checkHTTPClientUser(&user, r, connectionID); err != nil {
- sendAPIResponse(w, r, err, http.StatusText(http.StatusForbidden), http.StatusForbidden)
- return
- }
- connection := &Connection{
- BaseConnection: common.NewBaseConnection(connID, common.ProtocolHTTP, r.RemoteAddr, user),
- request: r,
- }
- common.Connections.Add(connection)
- defer common.Connections.Remove(connection.GetID())
- var filesList []string
- err = render.DecodeJSON(r.Body, &filesList)
- if err != nil {
- sendAPIResponse(w, r, err, "", http.StatusBadRequest)
- return
- }
- baseDir := "/"
- for idx := range filesList {
- filesList[idx] = util.CleanPath(filesList[idx])
- }
- w.Header().Set("Content-Disposition", "attachment; filename=\"sftpgo-download.zip\"")
- renderCompressedFiles(w, connection, baseDir, filesList)
- }
- func getUserPublicKeys(w http.ResponseWriter, r *http.Request) {
- claims, err := getTokenClaims(r)
- if err != nil || claims.Username == "" {
- sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
- return
- }
- user, err := dataprovider.UserExists(claims.Username)
- if err != nil {
- sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
- return
- }
- render.JSON(w, r, user.PublicKeys)
- }
- func setUserPublicKeys(w http.ResponseWriter, r *http.Request) {
- r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
- claims, err := getTokenClaims(r)
- if err != nil || claims.Username == "" {
- sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
- return
- }
- user, err := dataprovider.UserExists(claims.Username)
- if err != nil {
- sendAPIResponse(w, r, nil, "Unable to retrieve your user", getRespStatus(err))
- return
- }
- var publicKeys []string
- err = render.DecodeJSON(r.Body, &publicKeys)
- if err != nil {
- sendAPIResponse(w, r, err, "", http.StatusBadRequest)
- return
- }
- user.PublicKeys = publicKeys
- err = dataprovider.UpdateUser(&user)
- if err != nil {
- sendAPIResponse(w, r, err, "", getRespStatus(err))
- return
- }
- sendAPIResponse(w, r, err, "Public keys updated", http.StatusOK)
- }
- func changeUserPassword(w http.ResponseWriter, r *http.Request) {
- r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
- var pwd pwdChange
- err := render.DecodeJSON(r.Body, &pwd)
- if err != nil {
- sendAPIResponse(w, r, err, "", http.StatusBadRequest)
- return
- }
- err = doChangeUserPassword(r, pwd.CurrentPassword, pwd.NewPassword, pwd.NewPassword)
- if err != nil {
- sendAPIResponse(w, r, err, "", getRespStatus(err))
- return
- }
- sendAPIResponse(w, r, err, "Password updated", http.StatusOK)
- }
- func doChangeUserPassword(r *http.Request, currentPassword, newPassword, confirmNewPassword string) error {
- if currentPassword == "" || newPassword == "" || confirmNewPassword == "" {
- return util.NewValidationError("please provide the current password and the new one two times")
- }
- if newPassword != confirmNewPassword {
- return util.NewValidationError("the two password fields do not match")
- }
- if currentPassword == newPassword {
- return util.NewValidationError("the new password must be different from the current one")
- }
- claims, err := getTokenClaims(r)
- if err != nil || claims.Username == "" {
- return errors.New("invalid token claims")
- }
- user, err := dataprovider.CheckUserAndPass(claims.Username, currentPassword, util.GetIPFromRemoteAddress(r.RemoteAddr),
- common.ProtocolHTTP)
- if err != nil {
- return util.NewValidationError("current password does not match")
- }
- user.Password = newPassword
- return dataprovider.UpdateUser(&user)
- }
|