378 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			378 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
package http
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"net/http"
 | 
						|
	"os"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	fm "github.com/hacdias/filemanager"
 | 
						|
)
 | 
						|
 | 
						|
type modifyRequest struct {
 | 
						|
	What  string `json:"what"`  // Answer to: what data type?
 | 
						|
	Which string `json:"which"` // Answer to: which field?
 | 
						|
}
 | 
						|
 | 
						|
type modifyUserRequest struct {
 | 
						|
	*modifyRequest
 | 
						|
	Data *fm.User `json:"data"`
 | 
						|
}
 | 
						|
 | 
						|
// usersHandler is the entry point of the users API. It's just a router
 | 
						|
// to send the request to its
 | 
						|
func usersHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	// If the user isn't admin and isn't making a PUT
 | 
						|
	// request, then return forbidden.
 | 
						|
	if !c.User.Admin && r.Method != http.MethodPut {
 | 
						|
		return http.StatusForbidden, nil
 | 
						|
	}
 | 
						|
 | 
						|
	switch r.Method {
 | 
						|
	case http.MethodGet:
 | 
						|
		return usersGetHandler(c, w, r)
 | 
						|
	case http.MethodPost:
 | 
						|
		return usersPostHandler(c, w, r)
 | 
						|
	case http.MethodDelete:
 | 
						|
		return usersDeleteHandler(c, w, r)
 | 
						|
	case http.MethodPut:
 | 
						|
		return usersPutHandler(c, w, r)
 | 
						|
	}
 | 
						|
 | 
						|
	return http.StatusNotImplemented, nil
 | 
						|
}
 | 
						|
 | 
						|
// getUserID returns the id from the user which is present
 | 
						|
// in the request url. If the url is invalid and doesn't
 | 
						|
// contain a valid ID, it returns an fm.Error.
 | 
						|
func getUserID(r *http.Request) (int, error) {
 | 
						|
	// Obtains the ID in string from the URL and converts
 | 
						|
	// it into an integer.
 | 
						|
	sid := strings.TrimPrefix(r.URL.Path, "/")
 | 
						|
	sid = strings.TrimSuffix(sid, "/")
 | 
						|
	id, err := strconv.Atoi(sid)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	return id, nil
 | 
						|
}
 | 
						|
 | 
						|
// getUser returns the user which is present in the request
 | 
						|
// body. If the body is empty or the JSON is invalid, it
 | 
						|
// returns an fm.Error.
 | 
						|
func getUser(c *fm.Context, r *http.Request) (*fm.User, string, error) {
 | 
						|
	// Checks if the request body is empty.
 | 
						|
	if r.Body == nil {
 | 
						|
		return nil, "", fm.ErrEmptyRequest
 | 
						|
	}
 | 
						|
 | 
						|
	// Parses the request body and checks if it's well formed.
 | 
						|
	mod := &modifyUserRequest{}
 | 
						|
	err := json.NewDecoder(r.Body).Decode(mod)
 | 
						|
	if err != nil {
 | 
						|
		return nil, "", err
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if the request type is right.
 | 
						|
	if mod.What != "user" {
 | 
						|
		return nil, "", fm.ErrWrongDataType
 | 
						|
	}
 | 
						|
 | 
						|
	mod.Data.FileSystem = c.NewFS(mod.Data.Scope)
 | 
						|
	return mod.Data, mod.Which, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersGetHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	// Request for the default user data.
 | 
						|
	if r.URL.Path == "/base" {
 | 
						|
		return renderJSON(w, c.DefaultUser)
 | 
						|
	}
 | 
						|
 | 
						|
	// Request for the listing of users.
 | 
						|
	if r.URL.Path == "/" {
 | 
						|
		users, err := c.Store.Users.Gets(c.NewFS)
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		for _, u := range users {
 | 
						|
			// Removes the user password so it won't
 | 
						|
			// be sent to the front-end.
 | 
						|
			u.Password = ""
 | 
						|
		}
 | 
						|
 | 
						|
		sort.Slice(users, func(i, j int) bool {
 | 
						|
			return users[i].ID < users[j].ID
 | 
						|
		})
 | 
						|
 | 
						|
		return renderJSON(w, users)
 | 
						|
	}
 | 
						|
 | 
						|
	id, err := getUserID(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	u, err := c.Store.Users.Get(id, c.NewFS)
 | 
						|
	if err == fm.ErrExist {
 | 
						|
		return http.StatusNotFound, err
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	u.Password = ""
 | 
						|
	return renderJSON(w, u)
 | 
						|
}
 | 
						|
 | 
						|
func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	if r.URL.Path != "/" {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	u, _, err := getUser(c, r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if username isn't empty.
 | 
						|
	if u.Username == "" {
 | 
						|
		return http.StatusBadRequest, fm.ErrEmptyUsername
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if scope isn't empty.
 | 
						|
	if u.Scope == "" {
 | 
						|
		return http.StatusBadRequest, fm.ErrEmptyScope
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if password isn't empty.
 | 
						|
	if u.Password == "" {
 | 
						|
		return http.StatusBadRequest, fm.ErrEmptyPassword
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize rules if they're not initialized.
 | 
						|
	if u.Rules == nil {
 | 
						|
		u.Rules = []*fm.Rule{}
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize commands if not initialized.
 | 
						|
	if u.Commands == nil {
 | 
						|
		u.Commands = []string{}
 | 
						|
	}
 | 
						|
 | 
						|
	// It's a new user so the ID will be auto created.
 | 
						|
	if u.ID != 0 {
 | 
						|
		u.ID = 0
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if the scope exists.
 | 
						|
	if code, err := checkFS(u.Scope); err != nil {
 | 
						|
		return code, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Hashes the password.
 | 
						|
	pw, err := fm.HashPassword(u.Password)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	u.Password = pw
 | 
						|
 | 
						|
	// Saves the user to the database.
 | 
						|
	err = c.Store.Users.Save(u)
 | 
						|
	if err == fm.ErrExist {
 | 
						|
		return http.StatusConflict, err
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Set the Location header and return.
 | 
						|
	w.Header().Set("Location", "/settings/users/"+strconv.Itoa(u.ID))
 | 
						|
	w.WriteHeader(http.StatusCreated)
 | 
						|
	return 0, nil
 | 
						|
}
 | 
						|
 | 
						|
func checkFS(path string) (int, error) {
 | 
						|
	info, err := os.Stat(path)
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		if !os.IsNotExist(err) {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		err = os.MkdirAll(path, 0666)
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		return 0, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if !info.IsDir() {
 | 
						|
		return http.StatusBadRequest, errors.New("Scope is not a dir")
 | 
						|
	}
 | 
						|
 | 
						|
	return 0, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersDeleteHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	if r.URL.Path == "/" {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	id, err := getUserID(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Deletes the user from the database.
 | 
						|
	err = c.Store.Users.Delete(id)
 | 
						|
	if err == fm.ErrNotExist {
 | 
						|
		return http.StatusNotFound, fm.ErrNotExist
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	return http.StatusOK, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	// New users should be created on /api/users.
 | 
						|
	if r.URL.Path == "/" {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Gets the user ID from the URL and checks if it's valid.
 | 
						|
	id, err := getUserID(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if the user has permission to access this page.
 | 
						|
	if !c.User.Admin && id != c.User.ID {
 | 
						|
		return http.StatusForbidden, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Gets the user from the request body.
 | 
						|
	u, which, err := getUser(c, r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	// If we're updating the default user. Only for NoAuth
 | 
						|
	// implementations. Used to change the viewMode.
 | 
						|
	if id == 0 && c.NoAuth {
 | 
						|
		c.DefaultUser.ViewMode = u.ViewMode
 | 
						|
		return http.StatusOK, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Updates the CSS and locale.
 | 
						|
	if which == "partial" {
 | 
						|
		c.User.CSS = u.CSS
 | 
						|
		c.User.Locale = u.Locale
 | 
						|
		c.User.ViewMode = u.ViewMode
 | 
						|
 | 
						|
		err = c.Store.Users.Update(c.User, "CSS", "Locale", "ViewMode")
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		return http.StatusOK, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// Updates the Password.
 | 
						|
	if which == "password" {
 | 
						|
		if u.Password == "" {
 | 
						|
			return http.StatusBadRequest, fm.ErrEmptyPassword
 | 
						|
		}
 | 
						|
 | 
						|
		if id == c.User.ID && c.User.LockPassword {
 | 
						|
			return http.StatusForbidden, nil
 | 
						|
		}
 | 
						|
 | 
						|
		c.User.Password, err = fm.HashPassword(u.Password)
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		err = c.Store.Users.Update(c.User, "Password")
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		return http.StatusOK, nil
 | 
						|
	}
 | 
						|
 | 
						|
	// If can only be all.
 | 
						|
	if which != "all" {
 | 
						|
		return http.StatusBadRequest, fm.ErrInvalidUpdateField
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if username isn't empty.
 | 
						|
	if u.Username == "" {
 | 
						|
		return http.StatusBadRequest, fm.ErrEmptyUsername
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if filesystem isn't empty.
 | 
						|
	if u.Scope == "" {
 | 
						|
		return http.StatusBadRequest, fm.ErrEmptyScope
 | 
						|
	}
 | 
						|
 | 
						|
	// Checks if the scope exists.
 | 
						|
	if code, err := checkFS(u.Scope); err != nil {
 | 
						|
		return code, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize rules if they're not initialized.
 | 
						|
	if u.Rules == nil {
 | 
						|
		u.Rules = []*fm.Rule{}
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize commands if not initialized.
 | 
						|
	if u.Commands == nil {
 | 
						|
		u.Commands = []string{}
 | 
						|
	}
 | 
						|
 | 
						|
	// Gets the current saved user from the in-memory map.
 | 
						|
	suser, err := c.Store.Users.Get(id, c.NewFS)
 | 
						|
	if err == fm.ErrNotExist {
 | 
						|
		return http.StatusNotFound, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	u.ID = id
 | 
						|
 | 
						|
	// Changes the password if the request wants it.
 | 
						|
	if u.Password != "" {
 | 
						|
		pw, err := fm.HashPassword(u.Password)
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		u.Password = pw
 | 
						|
	} else {
 | 
						|
		u.Password = suser.Password
 | 
						|
	}
 | 
						|
 | 
						|
	// Updates the whole User struct because we always are supposed
 | 
						|
	// to send a new entire object.
 | 
						|
	err = c.Store.Users.Update(u)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	return http.StatusOK, nil
 | 
						|
}
 |