filebrowser/backend/http/users.go

293 lines
8.0 KiB
Go
Raw Normal View History

package http
import (
"encoding/json"
"fmt"
2024-11-21 00:15:30 +00:00
"io"
"net/http"
2024-08-03 15:34:12 +00:00
"reflect"
"sort"
"strconv"
"golang.org/x/text/cases"
"golang.org/x/text/language"
2020-05-31 23:12:36 +00:00
2024-12-17 00:01:55 +00:00
"github.com/gtsteffaniak/filebrowser/backend/errors"
"github.com/gtsteffaniak/filebrowser/backend/files"
"github.com/gtsteffaniak/filebrowser/backend/storage"
"github.com/gtsteffaniak/filebrowser/backend/users"
)
var (
NonModifiableFieldsForNonAdmin = []string{"Username", "Scope", "LockPassword", "Perm", "Commands", "Rules"}
)
// SortingSettings represents the sorting settings.
type Sorting struct {
By string `json:"by"`
Asc bool `json:"asc"`
}
2024-11-21 00:15:30 +00:00
type UserRequest struct {
What string `json:"what"`
Which []string `json:"which"`
Data *users.User `json:"data"`
}
2024-11-21 00:15:30 +00:00
// userGetHandler retrieves a user by ID.
// @Summary Retrieve a user by ID
// @Description Returns a user's details based on their ID.
// @Tags Users
// @Accept json
// @Produce json
// @Param id path int true "User ID" or "self"
// @Success 200 {object} users.User "User details"
// @Failure 403 {object} map[string]string "Forbidden"
// @Failure 404 {object} map[string]string "Not Found"
// @Failure 500 {object} map[string]string "Internal Server Error"
// @Router /api/users/{id} [get]
func userGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
givenUserIdString := r.URL.Query().Get("id")
// since api self is used to validate a logged in user
w.Header().Add("X-Renew-Token", "false")
var givenUserId uint
if givenUserIdString == "self" {
givenUserId = d.user.ID
} else if givenUserIdString == "" {
2025-01-05 19:05:33 +00:00
userList, err := store.Users.Gets(files.RootPaths["default"])
if err != nil {
return http.StatusInternalServerError, err
}
2024-11-21 00:15:30 +00:00
2025-01-05 19:05:33 +00:00
selfUserList := []*users.User{}
for _, u := range userList {
2024-11-21 00:15:30 +00:00
u.Password = ""
u.ApiKeys = nil
2025-01-05 19:05:33 +00:00
if u.ID == d.user.ID {
selfUserList = append(selfUserList, u)
}
}
2025-01-05 19:05:33 +00:00
sort.Slice(userList, func(i, j int) bool {
return userList[i].ID < userList[j].ID
2024-11-21 00:15:30 +00:00
})
2025-01-05 19:05:33 +00:00
if !d.user.Perm.Admin {
userList = selfUserList
}
return renderJSON(w, r, userList)
2024-11-21 00:15:30 +00:00
} else {
num, _ := strconv.ParseUint(givenUserIdString, 10, 32)
givenUserId = uint(num)
}
2024-11-21 00:15:30 +00:00
if givenUserId != d.user.ID && !d.user.Perm.Admin {
return http.StatusForbidden, nil
}
2024-11-21 00:15:30 +00:00
// Fetch the user details
2025-01-05 19:05:33 +00:00
u, err := store.Users.Get(files.RootPaths["default"], givenUserId)
if err == errors.ErrNotExist {
return http.StatusNotFound, err
}
if err != nil {
return http.StatusInternalServerError, err
}
2024-11-21 00:15:30 +00:00
// Remove the password from the response if the user is not an admin
u.Password = ""
2024-11-21 00:15:30 +00:00
u.ApiKeys = nil
if !d.user.Perm.Admin {
u.Scope = ""
}
2024-11-21 00:15:30 +00:00
return renderJSON(w, r, u)
2024-11-21 00:15:30 +00:00
}
// userDeleteHandler deletes a user by ID.
// @Summary Delete a user by ID
// @Description Deletes a user identified by their ID.
// @Tags Users
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Success 200 "User deleted successfully"
// @Failure 403 {object} map[string]string "Forbidden"
// @Failure 500 {object} map[string]string "Internal Server Error"
// @Router /api/users/{id} [delete]
func userDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
givenUserIdString := r.URL.Query().Get("id")
num, _ := strconv.ParseUint(givenUserIdString, 10, 32)
givenUserId := uint(num)
if givenUserId == d.user.ID {
return http.StatusForbidden, fmt.Errorf("cannot delete your own user")
}
if !d.user.Perm.Admin {
return http.StatusForbidden, fmt.Errorf("cannot delete users without admin permissions")
}
if givenUserId == 1 {
return http.StatusForbidden, fmt.Errorf("cannot delete the default admin user")
2024-11-21 00:15:30 +00:00
}
2024-11-21 00:15:30 +00:00
// Delete the user
err := store.Users.Delete(givenUserId)
2021-01-11 21:33:36 +00:00
if err != nil {
return errToStatus(err), err
}
return http.StatusOK, nil
2024-11-21 00:15:30 +00:00
}
// usersPostHandler creates a new user.
// @Summary Create a new user
// @Description Adds a new user to the system.
// @Tags Users
// @Accept json
// @Produce json
// @Param data body users.User true "User data to create a new user"
// @Success 201 {object} users.User "Created user"
// @Failure 400 {object} map[string]string "Bad Request"
// @Failure 500 {object} map[string]string "Internal Server Error"
// @Router /api/users [post]
func usersPostHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
if !d.user.Perm.Admin {
return http.StatusForbidden, nil
}
// Validate the user's scope
2025-01-05 19:05:33 +00:00
idx := files.GetIndex("default")
_, _, err := idx.GetRealPath(d.user.Scope)
2024-11-21 00:15:30 +00:00
if err != nil {
return http.StatusBadRequest, err
}
2024-11-21 00:15:30 +00:00
// Read the JSON body
body, err := io.ReadAll(r.Body)
if err != nil {
2024-11-21 00:15:30 +00:00
return http.StatusInternalServerError, err
}
defer r.Body.Close()
// Parse the JSON into the UserRequest struct
var req UserRequest
if err = json.Unmarshal(body, &req); err != nil {
return http.StatusBadRequest, err
}
if len(req.Which) != 0 {
return http.StatusBadRequest, nil
}
if req.Data.Password == "" {
return http.StatusBadRequest, errors.ErrEmptyPassword
}
2024-10-07 22:44:53 +00:00
err = storage.CreateUser(*req.Data, req.Data.Perm.Admin)
if err != nil {
return http.StatusInternalServerError, err
}
2023-02-16 08:11:12 +00:00
w.Header().Set("Location", "/settings/users/"+strconv.FormatUint(uint64(req.Data.ID), 10))
return http.StatusCreated, nil
2024-11-21 00:15:30 +00:00
}
2024-11-21 00:15:30 +00:00
// userPutHandler updates an existing user's details.
// @Summary Update a user's details
// @Description Updates the details of a user identified by ID.
// @Tags Users
// @Accept json
// @Produce json
// @Param id path int true "User ID"
// @Param data body users.User true "User data to update"
// @Success 200 {object} users.User "Updated user details"
// @Failure 400 {object} map[string]string "Bad Request"
// @Failure 403 {object} map[string]string "Forbidden"
// @Failure 500 {object} map[string]string "Internal Server Error"
// @Router /api/users/{id} [put]
func userPutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) {
givenUserIdString := r.URL.Query().Get("id")
num, _ := strconv.ParseUint(givenUserIdString, 10, 32)
givenUserId := uint(num)
if givenUserId != d.user.ID && !d.user.Perm.Admin {
return http.StatusForbidden, nil
}
// Validate the user's scope
2025-01-05 19:05:33 +00:00
idx := files.GetIndex("default")
_, _, err := idx.GetRealPath(d.user.Scope)
if err != nil {
return http.StatusBadRequest, err
}
2024-11-21 00:15:30 +00:00
// Read the JSON body
body, err := io.ReadAll(r.Body)
2024-08-24 22:02:33 +00:00
if err != nil {
2024-11-21 00:15:30 +00:00
return http.StatusInternalServerError, err
}
2024-11-21 00:15:30 +00:00
defer r.Body.Close()
2024-11-21 00:15:30 +00:00
// Parse the JSON into the UserRequest struct
var req UserRequest
if err = json.Unmarshal(body, &req); err != nil {
return http.StatusBadRequest, err
}
// If `Which` is not specified, default to updating all fields
2024-08-03 15:34:12 +00:00
if len(req.Which) == 0 || req.Which[0] == "all" {
req.Which = []string{}
v := reflect.ValueOf(req.Data)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}
2024-08-03 15:34:12 +00:00
t := v.Type()
2024-11-21 00:15:30 +00:00
// Dynamically populate fields to update
2024-08-03 15:34:12 +00:00
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
2024-09-16 21:01:16 +00:00
if field.Name == "Password" && req.Data.Password != "" {
req.Which = append(req.Which, field.Name)
} else if field.Name != "Password" && field.Name != "Fs" {
2024-08-03 15:34:12 +00:00
req.Which = append(req.Which, field.Name)
}
}
}
2024-11-21 00:15:30 +00:00
// Process the fields to update
for _, field := range req.Which {
// Title case field names
field = cases.Title(language.English, cases.NoLower).String(field)
// Handle password update
if field == "Password" {
if !d.user.Perm.Admin && d.user.LockPassword {
return http.StatusForbidden, nil
}
req.Data.Password, err = users.HashPwd(req.Data.Password)
if err != nil {
return http.StatusInternalServerError, err
}
}
2024-11-21 00:15:30 +00:00
// Prevent non-admins from modifying certain fields
for _, restrictedField := range NonModifiableFieldsForNonAdmin {
if !d.user.Perm.Admin && field == restrictedField {
return http.StatusForbidden, nil
}
}
}
2024-11-21 00:15:30 +00:00
// Perform the user update
err = store.Users.Update(req.Data, req.Which...)
if err != nil {
return http.StatusInternalServerError, err
}
2024-11-21 00:15:30 +00:00
// Return the updated user (with the password hidden) as JSON response
req.Data.Password = ""
return renderJSON(w, r, req.Data)
}