336 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			336 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
package filemanager
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"net/http"
 | 
						|
	"sort"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"github.com/asdine/storm"
 | 
						|
)
 | 
						|
 | 
						|
// usersHandler is the entry point of the users API. It's just a router
 | 
						|
// to send the request to its
 | 
						|
func usersHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	if r.URL.Path == "/change-password" {
 | 
						|
		return usersUpdatePassword(c, w, r)
 | 
						|
	}
 | 
						|
 | 
						|
	if r.URL.Path == "/change-css" {
 | 
						|
		return usersUpdateCSS(c, w, r)
 | 
						|
	}
 | 
						|
 | 
						|
	// If the user is admin and the HTTP Method is not
 | 
						|
	// PUT, then we return forbidden.
 | 
						|
	if !c.User.Admin {
 | 
						|
		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 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 error.
 | 
						|
func getUser(r *http.Request) (*User, error) {
 | 
						|
	if r.Body == nil {
 | 
						|
		return nil, errEmptyRequest
 | 
						|
	}
 | 
						|
 | 
						|
	u := &User{}
 | 
						|
 | 
						|
	err := json.NewDecoder(r.Body).Decode(u)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	return u, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersGetHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	// Request for the default user data.
 | 
						|
	if r.URL.Path == "/base" {
 | 
						|
		return renderJSON(w, c.FM.DefaultUser)
 | 
						|
	}
 | 
						|
 | 
						|
	// Request for the listing of users.
 | 
						|
	if r.URL.Path == "/" {
 | 
						|
		users := []User{}
 | 
						|
 | 
						|
		for _, user := range c.FM.Users {
 | 
						|
			// Copies the user info and removes its
 | 
						|
			// password so it won't be sent to the
 | 
						|
			// front-end.
 | 
						|
			u := *user
 | 
						|
			u.Password = ""
 | 
						|
			users = append(users, u)
 | 
						|
		}
 | 
						|
 | 
						|
		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
 | 
						|
	}
 | 
						|
 | 
						|
	// Searches for the user and prints the one who matches.
 | 
						|
	for _, user := range c.FM.Users {
 | 
						|
		if user.ID != id {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		u := *user
 | 
						|
		u.Password = ""
 | 
						|
		return renderJSON(w, u)
 | 
						|
	}
 | 
						|
 | 
						|
	// If there aren't any matches, return not found.
 | 
						|
	return http.StatusNotFound, errUserNotExist
 | 
						|
}
 | 
						|
 | 
						|
func usersPostHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	if r.URL.Path != "/" {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	u, err := getUser(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	// The username, password and scope cannot be empty.
 | 
						|
	if u.Username == "" || u.Password == "" || u.FileSystem == "" {
 | 
						|
		return http.StatusBadRequest, errors.New("username, password or scope is empty")
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize rules if they're not initialized.
 | 
						|
	if u.Rules == nil {
 | 
						|
		u.Rules = []*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
 | 
						|
	}
 | 
						|
 | 
						|
	// Hashes the password.
 | 
						|
	pw, err := hashPassword(u.Password)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	u.Password = pw
 | 
						|
 | 
						|
	// Saves the user to the database.
 | 
						|
	err = c.FM.db.Save(u)
 | 
						|
	if err == storm.ErrAlreadyExists {
 | 
						|
		return http.StatusConflict, errUserExist
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Saves the user to the memory.
 | 
						|
	c.FM.Users[u.Username] = u
 | 
						|
 | 
						|
	// Set the Location header and return.
 | 
						|
	w.Header().Set("Location", "/users/"+strconv.Itoa(u.ID))
 | 
						|
	w.WriteHeader(http.StatusCreated)
 | 
						|
	return 0, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersDeleteHandler(c *RequestContext, 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.FM.db.DeleteStruct(&User{ID: id})
 | 
						|
	if err == storm.ErrNotFound {
 | 
						|
		return http.StatusNotFound, errUserNotExist
 | 
						|
	}
 | 
						|
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	// Delete the user from the in-memory users map.
 | 
						|
	for _, user := range c.FM.Users {
 | 
						|
		if user.ID == id {
 | 
						|
			delete(c.FM.Users, user.Username)
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return http.StatusOK, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersUpdatePassword(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	if r.Method != http.MethodPut {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	u, err := getUser(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	if u.Password == "" {
 | 
						|
		return http.StatusBadRequest, errEmptyPassword
 | 
						|
	}
 | 
						|
 | 
						|
	pw, err := hashPassword(u.Password)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	c.User.Password = pw
 | 
						|
	err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "Password", pw)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	return http.StatusOK, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersUpdateCSS(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	if r.Method != http.MethodPut {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	u, err := getUser(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	c.User.CSS = u.CSS
 | 
						|
	err = c.FM.db.UpdateField(&User{ID: c.User.ID}, "CSS", u.CSS)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	return http.StatusOK, nil
 | 
						|
}
 | 
						|
 | 
						|
func usersPutHandler(c *RequestContext, w http.ResponseWriter, r *http.Request) (int, error) {
 | 
						|
	// New users should be created on /api/users.
 | 
						|
	if r.URL.Path == "/" {
 | 
						|
		return http.StatusMethodNotAllowed, nil
 | 
						|
	}
 | 
						|
 | 
						|
	id, err := getUserID(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	u, err := getUser(r)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusBadRequest, err
 | 
						|
	}
 | 
						|
 | 
						|
	// The username and the filesystem cannot be empty.
 | 
						|
	if u.Username == "" || u.FileSystem == "" {
 | 
						|
		return http.StatusBadRequest, errors.New("Username, password or scope are empty")
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize rules if they're not initialized.
 | 
						|
	if u.Rules == nil {
 | 
						|
		u.Rules = []*Rule{}
 | 
						|
	}
 | 
						|
 | 
						|
	// Initialize commands if not initialized.
 | 
						|
	if u.Commands == nil {
 | 
						|
		u.Commands = []string{}
 | 
						|
	}
 | 
						|
 | 
						|
	var ouser *User
 | 
						|
	for _, user := range c.FM.Users {
 | 
						|
		if user.ID == id {
 | 
						|
			ouser = user
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if ouser == nil {
 | 
						|
		return http.StatusNotFound, nil
 | 
						|
	}
 | 
						|
 | 
						|
	u.ID = id
 | 
						|
 | 
						|
	if u.Password == "" {
 | 
						|
		u.Password = ouser.Password
 | 
						|
	} else {
 | 
						|
		pw, err := hashPassword(u.Password)
 | 
						|
		if err != nil {
 | 
						|
			return http.StatusInternalServerError, err
 | 
						|
		}
 | 
						|
 | 
						|
		u.Password = pw
 | 
						|
	}
 | 
						|
 | 
						|
	if u.Permissions == nil {
 | 
						|
		u.Permissions = c.FM.DefaultUser.Permissions
 | 
						|
	}
 | 
						|
 | 
						|
	// Updates the whole User struct because we always are supposed
 | 
						|
	// to send a new entire object.
 | 
						|
	err = c.FM.db.Save(u)
 | 
						|
	if err != nil {
 | 
						|
		return http.StatusInternalServerError, err
 | 
						|
	}
 | 
						|
 | 
						|
	// If the user changed the username, delete the old user
 | 
						|
	// from the in-memory user map.
 | 
						|
	if ouser.Username != u.Username {
 | 
						|
		delete(c.FM.Users, ouser.Username)
 | 
						|
	}
 | 
						|
 | 
						|
	c.FM.Users[u.Username] = u
 | 
						|
	return http.StatusOK, nil
 | 
						|
}
 |