test(singleuser): add tests for singleuser apis

This commit is contained in:
hexxa 2020-12-06 14:32:45 +08:00
parent 2bcb337b4c
commit 24adbcbe63
11 changed files with 265 additions and 82 deletions

View file

@ -8,24 +8,24 @@ import (
"github.com/parnurzeal/gorequest"
)
type QSClient struct {
type FilesClient struct {
addr string
r *gorequest.SuperAgent
}
func NewQSClient(addr string) *QSClient {
func NewFilesClient(addr string) *FilesClient {
gr := gorequest.New()
return &QSClient{
return &FilesClient{
addr: addr,
r: gr,
}
}
func (cl *QSClient) url(urlpath string) string {
func (cl *FilesClient) url(urlpath string) string {
return fmt.Sprintf("%s%s", cl.addr, urlpath)
}
func (cl *QSClient) Create(filepath string, size int64) (gorequest.Response, string, []error) {
func (cl *FilesClient) Create(filepath string, size int64) (gorequest.Response, string, []error) {
return cl.r.Post(cl.url("/v1/fs/files")).
Send(fileshdr.CreateReq{
Path: filepath,
@ -34,13 +34,13 @@ func (cl *QSClient) Create(filepath string, size int64) (gorequest.Response, str
End()
}
func (cl *QSClient) Delete(filepath string) (gorequest.Response, string, []error) {
func (cl *FilesClient) Delete(filepath string) (gorequest.Response, string, []error) {
return cl.r.Delete(cl.url("/v1/fs/files")).
Param(fileshdr.FilePathQuery, filepath).
End()
}
func (cl *QSClient) Metadata(filepath string) (gorequest.Response, *fileshdr.MetadataResp, []error) {
func (cl *FilesClient) Metadata(filepath string) (gorequest.Response, *fileshdr.MetadataResp, []error) {
resp, body, errs := cl.r.Get(cl.url("/v1/fs/metadata")).
Param(fileshdr.FilePathQuery, filepath).
End()
@ -54,13 +54,13 @@ func (cl *QSClient) Metadata(filepath string) (gorequest.Response, *fileshdr.Met
return resp, mResp, nil
}
func (cl *QSClient) Mkdir(dirpath string) (gorequest.Response, string, []error) {
func (cl *FilesClient) Mkdir(dirpath string) (gorequest.Response, string, []error) {
return cl.r.Post(cl.url("/v1/fs/dirs")).
Send(fileshdr.MkdirReq{Path: dirpath}).
End()
}
func (cl *QSClient) Move(oldpath, newpath string) (gorequest.Response, string, []error) {
func (cl *FilesClient) Move(oldpath, newpath string) (gorequest.Response, string, []error) {
return cl.r.Patch(cl.url("/v1/fs/files/move")).
Send(fileshdr.MoveReq{
OldPath: oldpath,
@ -69,7 +69,7 @@ func (cl *QSClient) Move(oldpath, newpath string) (gorequest.Response, string, [
End()
}
func (cl *QSClient) UploadChunk(filepath string, content string, offset int64) (gorequest.Response, string, []error) {
func (cl *FilesClient) UploadChunk(filepath string, content string, offset int64) (gorequest.Response, string, []error) {
return cl.r.Patch(cl.url("/v1/fs/files/chunks")).
Send(fileshdr.UploadChunkReq{
Path: filepath,
@ -79,7 +79,7 @@ func (cl *QSClient) UploadChunk(filepath string, content string, offset int64) (
End()
}
func (cl *QSClient) UploadStatus(filepath string) (gorequest.Response, *fileshdr.UploadStatusResp, []error) {
func (cl *FilesClient) UploadStatus(filepath string) (gorequest.Response, *fileshdr.UploadStatusResp, []error) {
resp, body, errs := cl.r.Get(cl.url("/v1/fs/files/chunks")).
Param(fileshdr.FilePathQuery, filepath).
End()
@ -93,7 +93,7 @@ func (cl *QSClient) UploadStatus(filepath string) (gorequest.Response, *fileshdr
return resp, uResp, nil
}
func (cl *QSClient) Download(filepath string, headers map[string]string) (gorequest.Response, string, []error) {
func (cl *FilesClient) Download(filepath string, headers map[string]string) (gorequest.Response, string, []error) {
r := cl.r.Get(cl.url("/v1/fs/files/chunks")).
Param(fileshdr.FilePathQuery, filepath)
for key, val := range headers {
@ -102,7 +102,7 @@ func (cl *QSClient) Download(filepath string, headers map[string]string) (gorequ
return r.End()
}
func (cl *QSClient) List(dirPath string) (gorequest.Response, *fileshdr.ListResp, []error) {
func (cl *FilesClient) List(dirPath string) (gorequest.Response, *fileshdr.ListResp, []error) {
resp, body, errs := cl.r.Get(cl.url("/v1/fs/dirs")).
Param(fileshdr.ListDirQuery, dirPath).
End()

55
src/client/singleuser.go Normal file
View file

@ -0,0 +1,55 @@
package client
import (
"fmt"
"net/http"
su "github.com/ihexxa/quickshare/src/handlers/singleuserhdr"
"github.com/parnurzeal/gorequest"
)
type SingleUserClient struct {
addr string
r *gorequest.SuperAgent
}
func NewSingleUserClient(addr string) *SingleUserClient {
gr := gorequest.New()
return &SingleUserClient{
addr: addr,
r: gr,
}
}
func (cl *SingleUserClient) url(urlpath string) string {
return fmt.Sprintf("%s%s", cl.addr, urlpath)
}
func (cl *SingleUserClient) Login(user, pwd string) (*http.Response, string, []error) {
return cl.r.Post(cl.url("/v1/users/login")).
Send(su.LoginReq{
User: user,
Pwd: pwd,
}).
End()
}
func (cl *SingleUserClient) Logout(user string, token *http.Cookie) (*http.Response, string, []error) {
return cl.r.Post(cl.url("/v1/users/logout")).
Send(su.LogoutReq{
User: user,
}).
AddCookie(token).
End()
}
func (cl *SingleUserClient) SetPwd(user, oldPwd, newPwd string, token *http.Cookie) (*http.Response, string, []error) {
return cl.r.Patch(cl.url("/v1/users/pwd")).
Send(su.SetPwdReq{
User: user,
OldPwd: oldPwd,
NewPwd: newPwd,
}).
AddCookie(token).
End()
}

12
src/client/utils.go Normal file
View file

@ -0,0 +1,12 @@
package client
import "net/http"
func GetCookie(cookies []*http.Cookie, name string) *http.Cookie {
for _, c := range cookies {
if c.Name == name {
return c
}
}
return nil
}

View file

@ -37,6 +37,7 @@ func NewLocalFS(root string, defaultPerm uint32, opensLimit, openTTL int) *Local
if root == "" {
root = "."
}
return &LocalFS{
root: root,
defaultPerm: os.FileMode(defaultPerm),

View file

@ -71,22 +71,29 @@ func generatePwd() (string, error) {
return fmt.Sprintf("%x", sha1.Sum(buf[:size]))[:8], nil
}
func (h *SimpleUserHandlers) Init(userName string) (string, error) {
func (h *SimpleUserHandlers) Init(userName, pwd string) (string, error) {
if userName == "" {
return "", errors.New("user name can not be empty")
}
var err error
tmpPwd, err := generatePwd()
if pwd == "" {
tmpPwd, err := generatePwd()
if err != nil {
return "", err
}
pwd = tmpPwd
}
pwdHash, err := bcrypt.GenerateFromPassword([]byte(pwd), 10)
if err != nil {
return "", err
}
err = h.deps.KV().SetStringIn(UsersNs, userName, tmpPwd)
err = h.deps.KV().SetStringIn(UsersNs, userName, string(pwdHash))
if err != nil {
return "", err
}
err = h.deps.KV().SetStringIn(RolesNs, RoleParam, AdminRole)
err = h.deps.KV().SetStringIn(RolesNs, userName, AdminRole)
if err != nil {
return "", err
}
@ -95,37 +102,41 @@ func (h *SimpleUserHandlers) Init(userName string) (string, error) {
return "", err
}
return tmpPwd, nil
return pwd, nil
}
type LoginReq struct {
User string `json:"user"`
Pwd string `json:"pwd"`
}
func (h *SimpleUserHandlers) Login(c *gin.Context) {
user, ok1 := c.GetPostForm(UserParam)
pwd, ok2 := c.GetPostForm(PwdParam)
if !ok1 || !ok2 {
c.JSON(q.ErrResp(c, 401, ErrInvalidUser))
req := &LoginReq{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(q.ErrResp(c, 500, err))
return
}
expectedHash, ok := h.deps.KV().GetStringIn(UsersNs, user)
expectedHash, ok := h.deps.KV().GetStringIn(UsersNs, req.User)
if !ok {
c.JSON(q.ErrResp(c, 500, ErrInvalidConfig))
return
}
err := bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(pwd))
err := bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(req.Pwd))
if err != nil {
c.JSON(q.ErrResp(c, 401, ErrInvalidUser))
c.JSON(q.ErrResp(c, 401, err))
return
}
role, ok := h.deps.KV().GetStringIn(RolesNs, user)
role, ok := h.deps.KV().GetStringIn(RolesNs, req.User)
if !ok {
c.JSON(q.ErrResp(c, 500, ErrInvalidConfig))
c.JSON(q.ErrResp(c, 501, ErrInvalidConfig))
return
}
ttl := h.cfg.GrabInt("Users.CookieTTL")
token, err := h.deps.Token().ToToken(map[string]string{
UserParam: user,
UserParam: req.User,
RoleParam: role,
ExpireParam: fmt.Sprintf("%d", time.Now().Unix()+int64(ttl)),
})
@ -142,39 +153,53 @@ func (h *SimpleUserHandlers) Login(c *gin.Context) {
c.JSON(q.Resp(200))
}
type LogoutReq struct {
User string `json:"user"`
}
func (h *SimpleUserHandlers) Logout(c *gin.Context) {
req := &LogoutReq{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(q.ErrResp(c, 500, err))
return
}
// token alreay verified in the authn middleware
c.SetCookie(TokenCookie, "", 0, "/", "nohost", false, true)
c.JSON(q.Resp(200))
}
type SetPwdReq struct {
User string `json:"user"`
OldPwd string `json:"oldPwd"`
NewPwd string `json:"newPwd"`
}
func (h *SimpleUserHandlers) SetPwd(c *gin.Context) {
user, ok1 := c.GetPostForm(UserParam)
pwd1, ok2 := c.GetPostForm(PwdParam)
pwd2, ok3 := c.GetPostForm(NewPwdParam)
if !ok1 || !ok2 || !ok3 {
c.JSON(q.ErrResp(c, 401, ErrInvalidUser))
req := &SetPwdReq{}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(q.ErrResp(c, 400, err))
return
}
expectedHash, ok := h.deps.KV().GetStringIn(UsersNs, user)
expectedHash, ok := h.deps.KV().GetStringIn(UsersNs, req.User)
if !ok {
c.JSON(q.ErrResp(c, 500, ErrInvalidConfig))
return
}
err := bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(pwd1))
err := bcrypt.CompareHashAndPassword([]byte(expectedHash), []byte(req.OldPwd))
if err != nil {
c.JSON(q.ErrResp(c, 401, ErrInvalidUser))
return
}
newHash, err := bcrypt.GenerateFromPassword([]byte(pwd2), 10)
newHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPwd), 10)
if err != nil {
c.JSON(q.ErrResp(c, 500, errors.New("fail to set password")))
return
}
err = h.deps.KV().SetStringIn(UsersNs, user, string(newHash))
err = h.deps.KV().SetStringIn(UsersNs, req.User, string(newHash))
if err != nil {
c.JSON(q.ErrResp(c, 500, ErrInvalidConfig))
return

View file

@ -40,6 +40,7 @@ func (h *SimpleUserHandlers) Auth() gin.HandlerFunc {
RoleParam: "",
ExpireParam: "",
}
_, err = h.deps.Token().FromToken(token, claims)
if err != nil {
c.JSON(q.ErrResp(c, 401, err))
@ -54,8 +55,8 @@ func (h *SimpleUserHandlers) Auth() gin.HandlerFunc {
}
// visitor is only allowed to download
if claims[UserParam] != AdminRole && handlerName != "Download-fm" {
c.JSON(q.ErrResp(c, 401, err))
if claims[RoleParam] != AdminRole && handlerName != "Download-fm" {
c.JSON(q.Resp(401))
return
}
}

View file

@ -9,11 +9,12 @@ type FSConfig struct {
}
type UsersCfg struct {
EnableAuth bool `json:"enableAuth"`
DefaultAdmin string `json:"defaultAdmin" cfg:"env"`
CookieTTL int `json:"cookieTTL"`
CookieSecure bool `json:"cookieSecure"`
CookieHttpOnly bool `json:"cookieHttpOnly"`
EnableAuth bool `json:"enableAuth"`
DefaultAdmin string `json:"defaultAdmin" cfg:"env"`
DefaultAdminPwd string `json:"defaultAdminPwd" cfg:"env"`
CookieTTL int `json:"cookieTTL"`
CookieSecure bool `json:"cookieSecure"`
CookieHttpOnly bool `json:"cookieHttpOnly"`
}
type Secrets struct {
@ -48,11 +49,12 @@ func DefaultConfig() (string, error) {
OpenTTL: 60, // 1 min
},
Users: &UsersCfg{
EnableAuth: true,
DefaultAdmin: "",
CookieTTL: 3600 * 24 * 7, // 1 week
CookieSecure: false,
CookieHttpOnly: true,
EnableAuth: true,
DefaultAdmin: "",
DefaultAdminPwd: "",
CookieTTL: 3600 * 24 * 7, // 1 week
CookieSecure: false,
CookieHttpOnly: true,
},
Secrets: &Secrets{
TokenSecret: "",

View file

@ -70,7 +70,7 @@ func initDeps(cfg gocfg.ICfg) *depidx.Deps {
filesystem := local.NewLocalFS(rootPath, 0660, opensLimit, openTTL)
jwtEncDec := jwt.NewJWTEncDec(secret)
logger := simplelog.NewSimpleLogger()
kv := boltdbpvd.New(".", 1024)
kv := boltdbpvd.New(rootPath, 1024)
deps := depidx.NewDeps(cfg)
deps.SetFS(filesystem)
@ -101,11 +101,13 @@ func initHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.E
fmt.Scanf("%s", &adminName)
}
adminTmpPwd, err := userHdrs.Init(adminName)
adminPwd, _ := cfg.String("ENV.DEFAULTADMINPWD")
adminPwd, err := userHdrs.Init(adminName, adminPwd)
if err != nil {
return nil, err
}
fmt.Printf("%s is created, its password is %s, please update it after login\n", adminName, adminTmpPwd)
fmt.Printf("%s is created, its password is %s, please update it after login\n", adminName, adminPwd)
}
fileHdrs, err := fileshdr.NewFileHandlers(cfg, deps)
@ -122,6 +124,7 @@ func initHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.E
users := v1.Group("/users")
users.POST("/login", userHdrs.Login)
users.POST("/logout", userHdrs.Logout)
users.PATCH("/pwd", userHdrs.SetPwd)
filesSvc := v1.Group("/fs")
filesSvc.POST("/files", fileHdrs.Create)

View file

@ -9,39 +9,13 @@ import (
"testing"
"time"
"github.com/ihexxa/gocfg"
"github.com/ihexxa/quickshare/src/client"
"github.com/ihexxa/quickshare/src/handlers/fileshdr"
)
func startTestServer(config string) *Server {
defaultCfg, err := DefaultConfig()
if err != nil {
panic(err)
}
cfg, err := gocfg.New(NewConfig()).
Load(
gocfg.JSONStr(defaultCfg),
gocfg.JSONStr(config),
)
if err != nil {
panic(err)
}
srv, err := NewServer(cfg)
if err != nil {
panic(err)
}
go srv.Start()
return srv
}
func TestFileHandlers(t *testing.T) {
addr := "http://127.0.0.1:8888"
root := "./testData"
root := "testData"
chunkSize := 2
config := `{
"users": {
@ -51,16 +25,21 @@ func TestFileHandlers(t *testing.T) {
"debug": true
},
"fs": {
"root": "./testData"
"root": "testData"
}
}`
err := os.MkdirAll(root, 0700)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
srv := startTestServer(config)
defer srv.Shutdown()
// kv := srv.depsKVStore()
fs := srv.depsFS()
defer os.RemoveAll(root)
cl := client.NewQSClient(addr)
cl := client.NewFilesClient(addr)
// TODO: remove this
time.Sleep(500)

View file

@ -0,0 +1,78 @@
package server
import (
"os"
"testing"
"time"
"github.com/ihexxa/quickshare/src/client"
su "github.com/ihexxa/quickshare/src/handlers/singleuserhdr"
)
func TestSingleUserHandlers(t *testing.T) {
addr := "http://127.0.0.1:8888"
root := "testData"
config := `{
"users": {
"enableAuth": true
},
"server": {
"debug": true
},
"fs": {
"root": "testData"
}
}`
adminName := "qs"
adminPwd := "quicksh@re"
adminNewPwd := "quicksh@re2"
os.Setenv("DEFAULTADMIN", adminName)
os.Setenv("DEFAULTADMINPWD", adminPwd)
err := os.MkdirAll(root, 0700)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(root)
srv := startTestServer(config)
defer srv.Shutdown()
suCl := client.NewSingleUserClient(addr)
// fCl := client.NewFilesClient(addr)
// TODO: remove this
time.Sleep(1000)
t.Run("test single user APIs: Login-SetPwd-Logout-Login", func(t *testing.T) {
resp, _, errs := suCl.Login(adminName, adminPwd)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
token := client.GetCookie(resp.Cookies(), su.TokenCookie)
resp, _, errs = suCl.SetPwd(adminName, adminPwd, adminNewPwd, token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
resp, _, errs = suCl.Logout(adminName, token)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
resp, _, errs = suCl.Login(adminName, adminNewPwd)
if len(errs) > 0 {
t.Fatal(errs)
} else if resp.StatusCode != 200 {
t.Fatal(resp.StatusCode)
}
})
}

View file

@ -0,0 +1,27 @@
package server
import "github.com/ihexxa/gocfg"
func startTestServer(config string) *Server {
defaultCfg, err := DefaultConfig()
if err != nil {
panic(err)
}
cfg, err := gocfg.New(NewConfig()).
Load(
gocfg.JSONStr(defaultCfg),
gocfg.JSONStr(config),
)
if err != nil {
panic(err)
}
srv, err := NewServer(cfg)
if err != nil {
panic(err)
}
go srv.Start()
return srv
}