diff --git a/README.md b/README.md index 8f88f5d..68ee981 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,9 @@ [![Build](https://travis-ci.com/vidar-team/Cardinal.svg?branch=master)](https://travis-ci.org/vidar-team/Cardinal) [![GoReport](https://goreportcard.com/badge/github.com/vidar-team/Cardinal)](https://goreportcard.com/report/github.com/vidar-team/Cardinal) [![codecov](https://codecov.io/gh/vidar-team/Cardinal/branch/master/graph/badge.svg)](https://codecov.io/gh/vidar-team/Cardinal) + + ## 介绍 Cardinal 是由 Vidar-Team 开发的 AWD 比赛平台,使用 Go 编写。本程序可以作为 CTF 线下比赛平台,亦可用于团队内部 AWD 模拟练习。 @@ -83,7 +85,13 @@ chmod +x ./Cardinal 十分欢迎您和我们一起改进 Cardinal,您可以改进现有程序,加入新功能,完善文档,优化代码等。 -[![Contributors](http://ergatejs.implements.io/badges/contributors/vidar-team/Cardinal_1280_96_10.png)](https://github.com/vidar-team/Cardinal/graphs/contributors) + + + + + + + ## 协议与许可 diff --git a/go.mod b/go.mod index 375e206..30e871d 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module Cardinal +module github.com/vidar-team/Cardinal go 1.13 @@ -37,9 +37,11 @@ require ( github.com/theplant/cldr v0.0.0-20190423050709-9f76f7ce4ee8 // indirect github.com/vidar-team/Cardinal_frontend v0.0.0-20200408105230-89ca4c2e48db github.com/vidar-team/Cardinal_manager_frontend v0.0.4 + golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd golang.org/x/net v0.0.0-20190923162816-aa69164e4478 // indirect golang.org/x/sys v0.0.0-20191010194322-b09406accb47 // indirect golang.org/x/text v0.3.0 + golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.2.4 // indirect moul.io/http2curl v1.0.0 // indirect diff --git a/go.sum b/go.sum index 40e096b..da2cd13 100644 --- a/go.sum +++ b/go.sum @@ -174,6 +174,7 @@ golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/nt golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= diff --git a/src/bulletin.go b/src/bulletin.go index 8cf329b..c00f868 100644 --- a/src/bulletin.go +++ b/src/bulletin.go @@ -3,6 +3,8 @@ package main import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "strconv" ) @@ -26,7 +28,7 @@ type BulletinRead struct { func (s *Service) GetAllBulletins() (int, interface{}) { var bulletins []Bulletin s.Mysql.Model(&Bulletin{}).Order("`id` DESC").Find(&bulletins) - return s.makeSuccessJSON(bulletins) + return utils.MakeSuccessJSON(bulletins) } // NewBulletin is post new bulletin handler for manager. @@ -38,8 +40,8 @@ func (s *Service) NewBulletin(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } @@ -49,12 +51,12 @@ func (s *Service) NewBulletin(c *gin.Context) (int, interface{}) { Content: inputForm.Content, }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "bulletin.post_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "bulletin.post_error"), ) } tx.Commit() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "bulletin.post_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "bulletin.post_success")) } // EditBulletin is edit new bulletin handler for manager. @@ -67,16 +69,16 @@ func (s *Service) EditBulletin(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } var checkBulletin Bulletin s.Mysql.Where(&Bulletin{Model: gorm.Model{ID: inputForm.ID}}).Find(&checkBulletin) if checkBulletin.ID == 0 { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "bulletin.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "bulletin.not_found"), ) } @@ -87,45 +89,45 @@ func (s *Service) EditBulletin(c *gin.Context) (int, interface{}) { tx := s.Mysql.Begin() if tx.Model(&Bulletin{}).Where(&Bulletin{Model: gorm.Model{ID: inputForm.ID}}).Updates(&newBulletin).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50001, - s.I18n.T(c.GetString("lang"), "bulletin.put_error"), + return utils.MakeErrJSON(500, 50001, + locales.I18n.T(c.GetString("lang"), "bulletin.put_error"), ) } tx.Commit() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "bulletin.put_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "bulletin.put_success")) } // DeleteBulletin is delete new bulletin handler for manager. func (s *Service) DeleteBulletin(c *gin.Context) (int, interface{}) { idStr, ok := c.GetQuery("id") if !ok { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } id, err := strconv.Atoi(idStr) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), ) } var checkBulletin Bulletin s.Mysql.Where(&Bulletin{Model: gorm.Model{ID: uint(id)}}).Find(&checkBulletin) if checkBulletin.ID == 0 { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "bulletin.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "bulletin.not_found"), ) } tx := s.Mysql.Begin() if tx.Where("id = ?", id).Delete(&Bulletin{}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50002, - s.I18n.T(c.GetString("lang"), "bulletin.delete_error"), + return utils.MakeErrJSON(500, 50002, + locales.I18n.T(c.GetString("lang"), "bulletin.delete_error"), ) } tx.Commit() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "bulletin.delete_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "bulletin.delete_success")) } diff --git a/src/challenge.go b/src/challenge.go index ed2a7d1..bd9e176 100644 --- a/src/challenge.go +++ b/src/challenge.go @@ -3,6 +3,8 @@ package main import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "strconv" "time" ) @@ -10,8 +12,10 @@ import ( // Challenge is a gorm model for database table `challenges`, used to store the challenges like Web1, Pwn1. type Challenge struct { gorm.Model - Title string - BaseScore int + Title string + BaseScore int + AutoRefreshFlag bool + Command string } // SetVisible is setting challenge visible status handler. @@ -26,16 +30,16 @@ func (s *Service) SetVisible(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } var checkChallenge Challenge s.Mysql.Where(&Challenge{Model: gorm.Model{ID: inputForm.ID}}).Find(&checkChallenge) if checkChallenge.Title == "" { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "challenge.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "challenge.not_found"), ) } @@ -53,9 +57,9 @@ func (s *Service) SetVisible(c *gin.Context) (int, interface{}) { status = "visible" } s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.set_challenge_"+status, gin.H{"challenge": checkChallenge.Title})), + string(locales.I18n.T(c.GetString("lang"), "log.set_challenge_"+status, gin.H{"challenge": checkChallenge.Title})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "gamebox.visibility_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "gamebox.visibility_success")) } // GetAllChallenges get all challenges from the database. @@ -63,11 +67,13 @@ func (s *Service) GetAllChallenges() (int, interface{}) { var challenges []Challenge s.Mysql.Model(&Challenge{}).Find(&challenges) type resultStruct struct { - ID uint - CreatedAt time.Time - Title string - Visible bool - BaseScore int + ID uint + CreatedAt time.Time + Title string + Visible bool + BaseScore int + AutoRefreshFlag bool + Command string } var res []resultStruct @@ -79,92 +85,122 @@ func (s *Service) GetAllChallenges() (int, interface{}) { s.Mysql.Where(&GameBox{ChallengeID: v.ID}).Limit(1).Find(&gameBox) res = append(res, resultStruct{ - ID: v.ID, - CreatedAt: v.CreatedAt, - Title: v.Title, - Visible: gameBox.Visible, - BaseScore: v.BaseScore, + ID: v.ID, + CreatedAt: v.CreatedAt, + Title: v.Title, + Visible: gameBox.Visible, + BaseScore: v.BaseScore, + AutoRefreshFlag: v.AutoRefreshFlag, + Command: v.Command, }) } - return s.makeSuccessJSON(res) + return utils.MakeSuccessJSON(res) } // NewChallenge is new challenge handler for manager. func (s *Service) NewChallenge(c *gin.Context) (int, interface{}) { type InputForm struct { - Title string `binding:"required"` - BaseScore int `binding:"required"` + Title string `binding:"required"` + BaseScore int `binding:"required"` + AutoRefreshFlag bool + Command string } var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } + if inputForm.AutoRefreshFlag && inputForm.Command == "" { + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "challenge.empty_command")) + } + + if !inputForm.AutoRefreshFlag { + inputForm.Command = "" + } + newChallenge := &Challenge{ - Title: inputForm.Title, - BaseScore: inputForm.BaseScore, + Title: inputForm.Title, + BaseScore: inputForm.BaseScore, + AutoRefreshFlag: inputForm.AutoRefreshFlag, + Command: inputForm.Command, } var checkChallenge Challenge s.Mysql.Model(&Challenge{}).Where(&Challenge{Title: newChallenge.Title}).Find(&checkChallenge) if checkChallenge.Title != "" { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.post_repeat"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.post_repeat"), ) } tx := s.Mysql.Begin() if tx.Create(newChallenge).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "challenge.post_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "challenge.post_error"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.new_challenge", gin.H{"challenge": checkChallenge.Title})), + string(locales.I18n.T(c.GetString("lang"), "log.new_challenge", gin.H{"title": checkChallenge.Title})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "challenge.post_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "challenge.post_success")) } // EditChallenge is edit challenge handler for manager. func (s *Service) EditChallenge(c *gin.Context) (int, interface{}) { type InputForm struct { - ID uint `binding:"required"` - Title string `binding:"required"` - BaseScore int `binding:"required"` + ID uint `binding:"required"` + Title string `binding:"required"` + BaseScore int `binding:"required"` + AutoRefreshFlag bool + Command string } var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } + if inputForm.AutoRefreshFlag && inputForm.Command == "" { + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "challenge.empty_command")) + } + + // True off auto refresh flag, clean the command. + if !inputForm.AutoRefreshFlag { + inputForm.Command = "" + } + var checkChallenge Challenge s.Mysql.Where(&Challenge{Model: gorm.Model{ID: inputForm.ID}}).Find(&checkChallenge) if checkChallenge.Title == "" { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "challenge.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "challenge.not_found"), ) } - newChallenge := &Challenge{ - Title: inputForm.Title, - BaseScore: inputForm.BaseScore, + // For the `AutoRefreshFlag` is a boolean value, use map here. + editChallenge := map[string]interface{}{ + "Title": inputForm.Title, + "BaseScore": inputForm.BaseScore, + "AutoRefreshFlag": inputForm.AutoRefreshFlag, + "Command": inputForm.Command, } tx := s.Mysql.Begin() - if tx.Model(&Challenge{}).Where(&Challenge{Model: gorm.Model{ID: inputForm.ID}}).Updates(&newChallenge).RowsAffected != 1 { + if tx.Model(&Challenge{}).Where(&Challenge{Model: gorm.Model{ID: inputForm.ID}}).Updates(editChallenge).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50001, - s.I18n.T(c.GetString("lang"), "challenge.put_error"), + return utils.MakeErrJSON(500, 50001, + locales.I18n.T(c.GetString("lang"), "challenge.put_error"), ) } tx.Commit() @@ -184,29 +220,29 @@ func (s *Service) EditChallenge(c *gin.Context) (int, interface{}) { s.SetRankListTitle() } - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "challenge.post_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "challenge.put_success")) } // DeleteChallenge is delete challenge handler for manager. func (s *Service) DeleteChallenge(c *gin.Context) (int, interface{}) { idStr, ok := c.GetQuery("id") if !ok { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } id, err := strconv.Atoi(idStr) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), ) } var challenge Challenge s.Mysql.Where(&Challenge{Model: gorm.Model{ID: uint(id)}}).Find(&challenge) if challenge.Title == "" { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "challenge.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "challenge.not_found"), ) } @@ -215,14 +251,14 @@ func (s *Service) DeleteChallenge(c *gin.Context) (int, interface{}) { tx.Where("challenge_id = ?", uint(id)).Delete(&GameBox{}) if tx.Where("id = ?", uint(id)).Delete(&Challenge{}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50002, - s.I18n.T(c.GetString("lang"), "challenge.delete_error"), + return utils.MakeErrJSON(500, 50002, + locales.I18n.T(c.GetString("lang"), "challenge.delete_error"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.delete_challenge", gin.H{"challenge": challenge.Title})), + string(locales.I18n.T(c.GetString("lang"), "log.delete_challenge", gin.H{"title": challenge.Title})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "challenge.delete_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "challenge.delete_success")) } diff --git a/src/check.go b/src/check.go index b6e013e..91c3570 100644 --- a/src/check.go +++ b/src/check.go @@ -3,6 +3,8 @@ package main import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" ) // DownAction is a gorm model for database table `down_actions`. @@ -19,8 +21,8 @@ type DownAction struct { func (s *Service) CheckDown(c *gin.Context) (int, interface{}) { // Check down is forbidden if the competition isn't start. if s.Timer.Status != "on" { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.not_begin"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.not_begin"), ) } @@ -30,8 +32,8 @@ func (s *Service) CheckDown(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } @@ -42,8 +44,8 @@ func (s *Service) CheckDown(c *gin.Context) (int, interface{}) { Round: s.Timer.NowRound, }).Find(&repeatCheck) if repeatCheck.ID != 0 { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "check.repeat"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "check.repeat"), ) } @@ -51,8 +53,8 @@ func (s *Service) CheckDown(c *gin.Context) (int, interface{}) { var gameBox GameBox s.Mysql.Model(&GameBox{}).Where(&GameBox{Model: gorm.Model{ID: inputForm.GameBoxID}}).Find(&gameBox) if gameBox.ID == 0 { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "gamebox.not_found"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "gamebox.not_found"), ) } @@ -67,8 +69,8 @@ func (s *Service) CheckDown(c *gin.Context) (int, interface{}) { Round: s.Timer.NowRound, }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "general.server_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "general.server_error"), ) } tx.Commit() @@ -76,5 +78,5 @@ func (s *Service) CheckDown(c *gin.Context) (int, interface{}) { // Update the gamebox status in ranking list. s.SetRankList() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "general.success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "general.success")) } diff --git a/src/conf/config.go b/src/conf/config.go new file mode 100644 index 0000000..2540eae --- /dev/null +++ b/src/conf/config.go @@ -0,0 +1,30 @@ +package conf + +import ( + "github.com/BurntSushi/toml" + "github.com/vidar-team/Cardinal/src/locales" + "log" + "os" +) + +var conf *Config + +func init() { + if os.Getenv("TRAVIS") != "true" { + _, err := toml.DecodeFile("./conf/Cardinal.toml", &conf) + if err != nil { + log.Fatalln(err) + } + + log.Println(locales.I18n.T(conf.SystemLanguage, "config.load_success")) + } else { + // Travis CI Test, set the config in test code. + conf = new(Config) + log.Println("Test mode") + } +} + +// Get returns the config struct. +func Get() *Config { + return conf +} diff --git a/src/config.go b/src/conf/model.go similarity index 54% rename from src/config.go rename to src/conf/model.go index 0d7a659..d2817b5 100644 --- a/src/config.go +++ b/src/conf/model.go @@ -1,12 +1,8 @@ -package main +package conf -import ( - "github.com/BurntSushi/toml" - "log" - "time" -) +import "time" -// Config is the `cardinal.toml` config file struct. +// Config is the config of the cardinal. type Config struct { Base `toml:"base"` MySQL `toml:"mysql"` @@ -35,15 +31,3 @@ type MySQL struct { DBPassword string DBName string } - -// initConfig will decode the config file and put it into `s.Conf`, so we can get the config globally. -func (s *Service) initConfig() { - var conf *Config - _, err := toml.DecodeFile("./conf/Cardinal.toml", &conf) - if err != nil { - log.Fatalln(err) - } - - s.Conf = conf - log.Println(s.I18n.T(s.Conf.Base.SystemLanguage, "config.load_success")) -} diff --git a/src/file.go b/src/file.go index ecd65f9..0bcbdcf 100644 --- a/src/file.go +++ b/src/file.go @@ -3,14 +3,16 @@ package main import ( "github.com/gin-gonic/gin" "github.com/thanhpk/randstr" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" ) // UploadPicture is upload team logo handler for manager. func (s *Service) UploadPicture(c *gin.Context) (int, interface{}) { file, err := c.FormFile("picture") if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "file.select_picture"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "file.select_picture"), ) } fileExt := map[string]string{ @@ -26,9 +28,9 @@ func (s *Service) UploadPicture(c *gin.Context) (int, interface{}) { err = c.SaveUploadedFile(file, "./uploads/"+fileName) if err != nil { - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "general.server_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "general.server_error"), ) } - return s.makeSuccessJSON(fileName) + return utils.MakeSuccessJSON(fileName) } diff --git a/src/flag.go b/src/flag.go index 1d392da..79036e5 100644 --- a/src/flag.go +++ b/src/flag.go @@ -4,7 +4,12 @@ import ( "fmt" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "strconv" + "strings" + "sync" "time" ) @@ -36,23 +41,23 @@ type Flag struct { func (s *Service) SubmitFlag(c *gin.Context) (int, interface{}) { // Submit flag is forbidden if the competition isn't start. if s.Timer.Status != "on" { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.not_begin"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.not_begin"), ) } secretKey := c.GetHeader("Authorization") if secretKey == "" { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.invalid_token"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.invalid_token"), ) } var team Team s.Mysql.Model(&Team{}).Where(&Team{SecretKey: secretKey}).Find(&team) teamID := team.ID if teamID == 0 { - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.invalid_token"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.invalid_token"), ) } @@ -62,16 +67,16 @@ func (s *Service) SubmitFlag(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } var flagData Flag s.Mysql.Model(&Flag{}).Where(&Flag{Flag: inputForm.Flag, Round: s.Timer.NowRound}).Find(&flagData) // 注意判断是否为本轮 Flag if flagData.ID == 0 || teamID == flagData.TeamID { // 注意不允许提交自己的 flag - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "flag.error"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "flag.error"), ) } @@ -84,8 +89,8 @@ func (s *Service) SubmitFlag(c *gin.Context) (int, interface{}) { Round: flagData.Round, }).Find(&repeatAttackCheck) if repeatAttackCheck.ID != 0 { - return s.makeErrJSON(403, 40301, - s.I18n.T(c.GetString("lang"), "flag.repeat"), + return utils.MakeErrJSON(403, 40301, + locales.I18n.T(c.GetString("lang"), "flag.repeat"), ) } @@ -102,8 +107,8 @@ func (s *Service) SubmitFlag(c *gin.Context) (int, interface{}) { Round: flagData.Round, }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "flag.submit_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "flag.submit_error"), ) } tx.Commit() @@ -111,7 +116,7 @@ func (s *Service) SubmitFlag(c *gin.Context) (int, interface{}) { // Update the gamebox status in ranking list. s.SetRankList() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "flag.submit_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "flag.submit_success")) } // GetFlags get flags from the database for backstage manager. @@ -121,15 +126,15 @@ func (s *Service) GetFlags(c *gin.Context) (int, interface{}) { page, err := strconv.Atoi(pageStr) if err != nil || page <= 0 { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } per, err := strconv.Atoi(perStr) if err != nil || per <= 0 || per >= 100 { // 限制每页最多 100 条 - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } @@ -139,12 +144,28 @@ func (s *Service) GetFlags(c *gin.Context) (int, interface{}) { var flags []Flag s.Mysql.Model(&Flag{}).Offset((page - 1) * per).Limit(per).Find(&flags) - return s.makeSuccessJSON(gin.H{ + return utils.MakeSuccessJSON(gin.H{ "array": flags, "total": total, }) } +// ExportFlag exports the flags of a challenge. +func (s *Service) ExportFlag(c *gin.Context) (int, interface{}) { + challengeIDStr := c.DefaultQuery("id", "1") + + challengeID, err := strconv.Atoi(challengeIDStr) + if err != nil || challengeID <= 0 { + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), + ) + } + + var flags []Flag + s.Mysql.Model(&Flag{}).Where(&Flag{ChallengeID: uint(challengeID)}).Find(&flags) + return utils.MakeSuccessJSON(flags) +} + // GenerateFlag is the generate flag handler for manager. func (s *Service) GenerateFlag(c *gin.Context) (int, interface{}) { var gameBoxes []GameBox @@ -154,10 +175,11 @@ func (s *Service) GenerateFlag(c *gin.Context) (int, interface{}) { // Delete all the flags in the table. s.Mysql.Unscoped().Delete(&Flag{}) + salt := utils.Sha1Encode(conf.Get().Salt) for round := 1; round <= s.Timer.TotalRound; round++ { // Flag = FlagPrefix + hmacSha1(TeamID + | + GameBoxID + | + Round, sha1(salt)) + FlagSuffix for _, gameBox := range gameBoxes { - flag := s.Conf.FlagPrefix + s.hmacSha1Encode(fmt.Sprintf("%d|%d|%d", gameBox.TeamID, gameBox.ID, round), s.sha1Encode(s.Conf.Salt)) + s.Conf.FlagSuffix + flag := conf.Get().FlagPrefix + utils.HmacSha1Encode(fmt.Sprintf("%d|%d|%d", gameBox.TeamID, gameBox.ID, round), salt) + conf.Get().FlagSuffix s.Mysql.Create(&Flag{ TeamID: gameBox.TeamID, GameBoxID: gameBox.ID, @@ -172,7 +194,73 @@ func (s *Service) GenerateFlag(c *gin.Context) (int, interface{}) { s.Mysql.Model(&Flag{}).Count(&count) endTime := time.Now().UnixNano() s.NewLog(WARNING, "system", - string(s.I18n.T(c.GetString("lang"), "log.generate_flag", gin.H{"total": count, "time": float64(endTime-startTime) / float64(time.Second)})), + string(locales.I18n.T(c.GetString("lang"), "log.generate_flag", gin.H{"total": count, "time": float64(endTime-startTime) / float64(time.Second)})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "flag.generate_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "flag.generate_success")) +} + +func (s *Service) refreshFlag() { + // Get the auto refresh flag challenges. + var challenges []Challenge + s.Mysql.Model(&Challenge{}).Where(&Challenge{AutoRefreshFlag: true}).Find(&challenges) + + for _, challenge := range challenges { + var gameboxes []GameBox + s.Mysql.Model(&GameBox{}).Where(&GameBox{ChallengeID: challenge.ID}).Find(&gameboxes) + + for _, gamebox := range gameboxes { + go func(gamebox GameBox, challenge Challenge) { + var flag Flag + s.Mysql.Model(&Flag{}).Where(&Flag{ + TeamID: gamebox.TeamID, + GameBoxID: gamebox.ID, + Round: s.Timer.NowRound, + }).Find(&flag) + // Replace the flag placeholder. + command := strings.ReplaceAll(challenge.Command, "{{FLAG}}", flag.Flag) + err := utils.SSHExecute(gamebox.IP, gamebox.SSHPort, gamebox.SSHUser, gamebox.SSHPassword, command) + if err != nil { + s.NewLog(IMPORTANT, "ssh_error", fmt.Sprintf("Team:%d Gamebox:%d Round:%d SSH 更新 Flag 失败:%v", gamebox.TeamID, gamebox.ID, s.Timer.NowRound, err.Error())) + } + + }(gamebox, challenge) + } + } +} + +func (s *Service) testSSH(c *gin.Context) (int, interface{}) { + var challenges []Challenge + s.Mysql.Model(&Challenge{}).Where(&Challenge{AutoRefreshFlag: true}).Find(&challenges) + + type errorMessage struct { + TeamID uint + ChallengeID uint + GameBoxID uint + Error string + } + var errs []errorMessage + + wg := sync.WaitGroup{} + for _, challenge := range challenges { + var gameboxes []GameBox + s.Mysql.Model(&GameBox{}).Where(&GameBox{ChallengeID: challenge.ID}).Find(&gameboxes) + + for _, gamebox := range gameboxes { + wg.Add(1) + go func(gamebox GameBox, challenge Challenge) { + defer wg.Done() + err := utils.SSHExecute(gamebox.IP, gamebox.SSHPort, gamebox.SSHUser, gamebox.SSHPassword, "whoami") + if err != nil { + errs = append(errs, errorMessage{ + TeamID: gamebox.TeamID, + ChallengeID: challenge.ID, + GameBoxID: gamebox.ID, + Error: err.Error(), + }) + } + }(gamebox, challenge) + } + } + wg.Wait() + return utils.MakeSuccessJSON(errs) } diff --git a/src/fs.go b/src/frontend/fs.go similarity index 61% rename from src/fs.go rename to src/frontend/fs.go index 2604aa1..a4b007d 100644 --- a/src/fs.go +++ b/src/frontend/fs.go @@ -1,27 +1,27 @@ -package main +package frontend import ( - frontend "github.com/vidar-team/Cardinal_frontend/dist" + challenger "github.com/vidar-team/Cardinal_frontend/dist" manager "github.com/vidar-team/Cardinal_manager_frontend/dist" "net/http" "strings" ) -// FrontendFS is the file system struct of challengers' frontend. -type FrontendFS struct { +type frontendFS struct { frontendFS http.FileSystem managerFS http.FileSystem } -func frontendFS() *FrontendFS { - return &FrontendFS{ - frontendFS: frontend.New(), +// FS is the filesystem of the frontend. +func FS() *frontendFS { + return &frontendFS{ + frontendFS: challenger.New(), managerFS: manager.New(), } } // Open: open file. -func (f *FrontendFS) Open(name string) (http.File, error) { +func (f *frontendFS) Open(name string) (http.File, error) { if strings.HasPrefix(name, "/manager") { return f.managerFS.Open(name) } @@ -29,7 +29,7 @@ func (f *FrontendFS) Open(name string) (http.File, error) { } // Exists: check if the file exist. -func (f *FrontendFS) Exists(prefix string, filePath string) bool { +func (f *frontendFS) Exists(prefix string, filePath string) bool { if strings.HasPrefix(filePath, "/manager") { if _, err := f.managerFS.Open(filePath); err != nil { return false diff --git a/src/gamebox.go b/src/gamebox.go index d426fce..d1a311a 100644 --- a/src/gamebox.go +++ b/src/gamebox.go @@ -3,6 +3,8 @@ package main import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "math" "strconv" ) @@ -15,6 +17,9 @@ type GameBox struct { IP string Port string + SSHPort string + SSHUser string + SSHPassword string Description string Visible bool Score float64 // The score can be negative. @@ -25,7 +30,7 @@ type GameBox struct { // GetSelfGameBoxes returns the gameboxes which belong to the team. func (s *Service) GetSelfGameBoxes(c *gin.Context) (int, interface{}) { if s.Timer.Status == "wait" { - return s.makeSuccessJSON([]int{}) + return utils.MakeSuccessJSON([]int{}) } var gameBoxes []struct { @@ -46,7 +51,7 @@ func (s *Service) GetSelfGameBoxes(c *gin.Context) (int, interface{}) { s.Mysql.Model(&Challenge{}).Where(&Challenge{Model: gorm.Model{ID: gameBox.ChallengeID}}).Find(&challenge) gameBoxes[index].Title = challenge.Title } - return s.makeSuccessJSON(gameBoxes) + return utils.MakeSuccessJSON(gameBoxes) } // GetGameBoxes returns the gameboxes for manager. @@ -56,14 +61,14 @@ func (s *Service) GetGameBoxes(c *gin.Context) (int, interface{}) { page, err := strconv.Atoi(pageStr) if err != nil || page <= 0 { - return s.makeErrJSON(400, 40002, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40002, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } perPage, err := strconv.Atoi(perPageStr) if err != nil || perPage <= 0 { - return s.makeErrJSON(400, 40002, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40002, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } @@ -72,7 +77,7 @@ func (s *Service) GetGameBoxes(c *gin.Context) (int, interface{}) { var gameBox []GameBox s.Mysql.Model(&GameBox{}).Offset((page - 1) * perPage).Limit(perPage).Find(&gameBox) - return s.makeSuccessJSON(gin.H{ + return utils.MakeSuccessJSON(gin.H{ "Data": gameBox, "Total": total, "TotalPage": math.Ceil(float64(total / perPage)), @@ -86,13 +91,18 @@ func (s *Service) NewGameBoxes(c *gin.Context) (int, interface{}) { TeamID uint `binding:"required"` IP string `binding:"required"` Port string `binding:"required"` + SSHPort string + SSHUser string + SSHPassword string Description string `binding:"required"` + + Score float64 // not for form } - var inputForm []InputForm + var inputForm []*InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } @@ -100,18 +110,30 @@ func (s *Service) NewGameBoxes(c *gin.Context) (int, interface{}) { var count int // Check the ChallengeID - s.Mysql.Model(&Challenge{}).Where(&Challenge{Model: gorm.Model{ID: item.ChallengeID}}).Count(&count) - if count != 1 { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "challenge.not_found"), + var challenge Challenge + s.Mysql.Model(&Challenge{}).Where(&Challenge{Model: gorm.Model{ID: item.ChallengeID}}).Find(&challenge) + if challenge.ID == 0 { + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "challenge.not_found"), ) } + // Set the default score. + item.Score = float64(challenge.BaseScore) + + // Check SSH config + if challenge.AutoRefreshFlag { + if item.SSHPort == "" || item.SSHUser == "" || item.SSHPassword == "" { + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "gamebox.auto_refresh_flag_error"), + ) + } + } // Check the TeamID s.Mysql.Model(&Team{}).Where(&Team{Model: gorm.Model{ID: item.TeamID}}).Count(&count) if count != 1 { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "team.not_found"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "team.not_found"), ) } @@ -119,8 +141,8 @@ func (s *Service) NewGameBoxes(c *gin.Context) (int, interface{}) { // since every team should have only one gamebox for each challenge. s.Mysql.Model(GameBox{}).Where(&GameBox{ChallengeID: item.ChallengeID, TeamID: item.TeamID}).Count(&count) if count != 0 { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "gamebox.repeat"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "gamebox.repeat"), ) } } @@ -132,21 +154,25 @@ func (s *Service) NewGameBoxes(c *gin.Context) (int, interface{}) { TeamID: item.TeamID, IP: item.IP, Port: item.Port, + SSHPort: item.SSHPort, + SSHUser: item.SSHUser, + SSHPassword: item.SSHPassword, + Score: item.Score, Description: item.Description, } if tx.Create(newGameBox).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "gamebox.post_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "gamebox.post_error"), ) } } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.new_gamebox", gin.H{"count": len(inputForm)})), + string(locales.I18n.T(c.GetString("lang"), "log.new_gamebox", gin.H{"count": len(inputForm)})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "gamebox.post_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "gamebox.post_success")) } // EditGameBox is edit gamebox handler for manager. @@ -156,13 +182,16 @@ func (s *Service) EditGameBox(c *gin.Context) (int, interface{}) { IP string `binding:"required"` Port string `binding:"required"` + SSHPort string + SSHUser string + SSHPassword string Description string `binding:"required"` } var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } @@ -170,14 +199,17 @@ func (s *Service) EditGameBox(c *gin.Context) (int, interface{}) { if tx.Model(&GameBox{}).Where(&GameBox{Model: gorm.Model{ID: inputForm.ID}}).Updates(&GameBox{ IP: inputForm.IP, Port: inputForm.Port, + SSHPort: inputForm.SSHPort, + SSHUser: inputForm.SSHUser, + SSHPassword: inputForm.SSHPassword, Description: inputForm.Description, }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50001, - s.I18n.T(c.GetString("lang"), "gamebox.put_error"), + return utils.MakeErrJSON(500, 50001, + locales.I18n.T(c.GetString("lang"), "gamebox.put_error"), ) } tx.Commit() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "gamebox.put_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "gamebox.put_success")) } diff --git a/src/healthy.go b/src/healthy.go index 59ae48e..79d4805 100644 --- a/src/healthy.go +++ b/src/healthy.go @@ -2,6 +2,8 @@ package main import ( "fmt" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/locales" "math" "strconv" ) @@ -14,10 +16,10 @@ func (s *Service) HealthyCheck() { previousRoundScore := s.PreviousRoundScore() if math.Abs(previousRoundScore) != 0 { // If the previous round total score is not equal zero, maybe all the teams were checked down. - if previousRoundScore != float64(-s.Conf.CheckDownScore*teamCount) { + if previousRoundScore != float64(-conf.Get().CheckDownScore*teamCount) { // Maybe there are some mistakes in previous round score. s.NewLog(IMPORTANT, "healthy_check", - string(s.I18n.T(s.Conf.Base.SystemLanguage, "healthy.previous_round_non_zero_error")), + string(locales.I18n.T(conf.Get().SystemLanguage, "healthy.previous_round_non_zero_error")), ) } } @@ -25,10 +27,10 @@ func (s *Service) HealthyCheck() { totalScore := s.TotalScore() if math.Abs(totalScore) != 0 { // If sum all the scores but it is not equal zero, maybe all the teams were checked down in some rounds. - if int(totalScore)%(s.Conf.CheckDownScore*teamCount) != 0 { + if int(totalScore)%(conf.Get().CheckDownScore*teamCount) != 0 { // Maybe there are some mistakes. s.NewLog(IMPORTANT, "healthy_check", - string(s.I18n.T(s.Conf.Base.SystemLanguage, "healthy.total_score_non_zero_error")), + string(locales.I18n.T(conf.Get().SystemLanguage, "healthy.total_score_non_zero_error")), ) } } diff --git a/src/install.go b/src/install.go index 186c5ca..38b1e12 100644 --- a/src/install.go +++ b/src/install.go @@ -5,6 +5,9 @@ import ( "errors" "fmt" "github.com/thanhpk/randstr" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "io/ioutil" "log" "os" @@ -45,7 +48,7 @@ DBName="{{ .DBName }}" func (s *Service) install() { // Check `uploads` folder exist - if !IsExist("./uploads") { + if !utils.FileIsExist("./uploads") { err := os.Mkdir("./uploads", os.ModePerm) if err != nil { log.Fatalln(err) @@ -53,7 +56,7 @@ func (s *Service) install() { } // Check `conf` folder exist - if !IsExist("./conf") { + if !utils.FileIsExist("./conf") { err := os.Mkdir("./conf", os.ModePerm) if err != nil { log.Fatalln(err) @@ -61,7 +64,7 @@ func (s *Service) install() { } // Check `locales` folder exist - if !IsExist("./locales") { + if !utils.FileIsExist("./locales") { err := os.Mkdir("./locales", os.ModePerm) if err != nil { log.Fatalln(err) @@ -83,7 +86,7 @@ func (s *Service) install() { log.Fatalln("Can not find the language file!") } - if !IsExist("./conf/Cardinal.toml") { + if !utils.FileIsExist("./conf/Cardinal.toml") { log.Println("Please select a preferred language for the installation guide:") for index, lang := range languages { fmt.Printf("%s - %s\n", index, lang) @@ -94,7 +97,7 @@ func (s *Service) install() { index := "0" var err = errors.New("") for languages[index] == "" { - InputString(&index, "type 1, 2... to select") + utils.InputString(&index, "type 1, 2... to select") } content, err := s.GenerateConfigFileGuide(languages[index]) @@ -105,7 +108,7 @@ func (s *Service) install() { if err != nil { log.Fatalln(err) } - log.Println(s.I18n.T(languages[index], "install.create_config_success")) + log.Println(locales.I18n.T(languages[index], "install.create_config_success")) } } @@ -124,14 +127,14 @@ func (s *Service) GenerateConfigFileGuide(lang string) ([]byte, error) { DBName: "cardinal", } - log.Println(s.I18n.T(lang, "install.greet")) + log.Println(locales.I18n.T(lang, "install.greet")) - InputString(&input.Title, string(s.I18n.T(lang, "install.input_title"))) + utils.InputString(&input.Title, string(locales.I18n.T(lang, "install.input_title"))) var beginTime time.Time err := errors.New("") for err != nil { - InputString(&input.BeginTime, string(s.I18n.T(lang, "install.begin_time"))) + utils.InputString(&input.BeginTime, string(locales.I18n.T(lang, "install.begin_time"))) beginTime, err = time.ParseInLocation("2006-01-02 15:04:05", input.BeginTime, time.Local) } input.BeginTime = beginTime.Format(time.RFC3339) @@ -139,21 +142,21 @@ func (s *Service) GenerateConfigFileGuide(lang string) ([]byte, error) { var endTime time.Time err = errors.New("") for err != nil { - InputString(&input.EndTime, string(s.I18n.T(lang, "install.end_time"))) + utils.InputString(&input.EndTime, string(locales.I18n.T(lang, "install.end_time"))) endTime, err = time.ParseInLocation("2006-01-02 15:04:05 ", input.EndTime, time.Local) } input.EndTime = endTime.Format(time.RFC3339) - InputString(&input.Duration, string(s.I18n.T(lang, "install.duration"))) - InputString(&input.Port, string(s.I18n.T(lang, "install.port"))) - InputString(&input.FlagPrefix, string(s.I18n.T(lang, "install.flag_prefix"))) - InputString(&input.FlagSuffix, string(s.I18n.T(lang, "install.flag_suffix"))) - InputString(&input.CheckDownScore, string(s.I18n.T(lang, "install.checkdown_score"))) - InputString(&input.AttackScore, string(s.I18n.T(lang, "install.attack_score"))) - InputString(&input.DBHost, string(s.I18n.T(lang, "install.db_host"))) - InputString(&input.DBUsername, string(s.I18n.T(lang, "install.db_username"))) - InputString(&input.DBPassword, string(s.I18n.T(lang, "install.db_password"))) - InputString(&input.DBName, string(s.I18n.T(lang, "install.db_name"))) + utils.InputString(&input.Duration, string(locales.I18n.T(lang, "install.duration"))) + utils.InputString(&input.Port, string(locales.I18n.T(lang, "install.port"))) + utils.InputString(&input.FlagPrefix, string(locales.I18n.T(lang, "install.flag_prefix"))) + utils.InputString(&input.FlagSuffix, string(locales.I18n.T(lang, "install.flag_suffix"))) + utils.InputString(&input.CheckDownScore, string(locales.I18n.T(lang, "install.checkdown_score"))) + utils.InputString(&input.AttackScore, string(locales.I18n.T(lang, "install.attack_score"))) + utils.InputString(&input.DBHost, string(locales.I18n.T(lang, "install.db_host"))) + utils.InputString(&input.DBUsername, string(locales.I18n.T(lang, "install.db_username"))) + utils.InputString(&input.DBPassword, string(locales.I18n.T(lang, "install.db_password"))) + utils.InputString(&input.DBName, string(locales.I18n.T(lang, "install.db_name"))) // Generate Salt input.Salt = randstr.String(64) @@ -176,13 +179,13 @@ func (s *Service) initManager() { if managerCount == 0 { // Create manager account if managers table is empty. var managerName, managerPassword string - InputString(&managerName, string(s.I18n.T(s.Conf.Base.SystemLanguage, "install.manager_name"))) - InputString(&managerPassword, string(s.I18n.T(s.Conf.Base.SystemLanguage, "install.manager_password"))) + utils.InputString(&managerName, string(locales.I18n.T(conf.Get().SystemLanguage, "install.manager_name"))) + utils.InputString(&managerPassword, string(locales.I18n.T(conf.Get().SystemLanguage, "install.manager_password"))) s.Mysql.Create(&Manager{ Name: managerName, - Password: s.addSalt(managerPassword), + Password: utils.AddSalt(managerPassword), }) - s.NewLog(WARNING, "system", string(s.I18n.T(s.Conf.Base.SystemLanguage, "install.manager_success"))) - log.Println(s.I18n.T(s.Conf.Base.SystemLanguage, "install.manager_success")) + s.NewLog(WARNING, "system", string(locales.I18n.T(conf.Get().SystemLanguage, "install.manager_success"))) + log.Println(locales.I18n.T(conf.Get().SystemLanguage, "install.manager_success")) } } diff --git a/src/locales/en-US.yml b/src/locales/en-US.yml index b218644..5bbea70 100644 --- a/src/locales/en-US.yml +++ b/src/locales/en-US.yml @@ -112,6 +112,7 @@ en-US: repeat: "Duplicated Admin Username" update_token_fail: "Update Admin Token Failed!" update_password_fail: "Edit Admin Password Failed!" + manager_required: "Manager Account Required" log: new_challenge: "New Challenge [{{.title}}] Created." diff --git a/src/i18n.go b/src/locales/i18n.go similarity index 71% rename from src/i18n.go rename to src/locales/i18n.go index d3591bb..04cb259 100644 --- a/src/i18n.go +++ b/src/locales/i18n.go @@ -1,4 +1,4 @@ -package main +package locales import ( "github.com/gin-gonic/gin" @@ -7,16 +7,17 @@ import ( "golang.org/x/text/language" ) -func (s *Service) initI18n() { - I18n := i18n.New( +// I18n is the i18n constant. +var I18n *i18n.I18n + +func init() { + I18n = i18n.New( yaml.New("./locales"), ) - - s.I18n = I18n } -// I18nMiddleware is an i18n middleware. Get client language from Accept-Language header. -func (s *Service) I18nMiddleware() gin.HandlerFunc { +// Middleware is an i18n middleware. Get client language from Accept-Language header. +func Middleware() gin.HandlerFunc { return func(c *gin.Context) { acceptLanguages := c.GetHeader("Accept-Language") languages, _, err := language.ParseAcceptLanguage(acceptLanguages) diff --git a/src/locales/zh-CN.yml b/src/locales/zh-CN.yml index 58902e6..8f02f15 100644 --- a/src/locales/zh-CN.yml +++ b/src/locales/zh-CN.yml @@ -29,6 +29,7 @@ zh-CN: delete_error: "删除题目失败!" delete_success: "删除题目成功!" not_found: "题目不存在!" + empty_command: "命令不能为空!" check: repeat: "重复 Check,已忽略" @@ -54,6 +55,7 @@ zh-CN: visibility_success: "修改靶机可见状态成功!" repeat: "存在重复添加的靶机,请检查" not_found: "靶机不存在!" + auto_refresh_flag_error: "靶机 SSH 设置为空" team: post_error: "添加队伍失败!" @@ -112,6 +114,7 @@ zh-CN: repeat: "管理员名称重复" update_token_fail: "更新管理员 Token 失败!" update_password_fail: "修改管理员密码失败!" + manager_required: "需要管理员权限账号" log: new_challenge: "新的题目 [{{.title}}] 被创建" diff --git a/src/log.go b/src/log.go index 1ab1969..0c74101 100644 --- a/src/log.go +++ b/src/log.go @@ -1,6 +1,7 @@ package main import ( + "github.com/vidar-team/Cardinal/src/utils" "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" "runtime" @@ -35,7 +36,7 @@ func (s *Service) NewLog(level int, kind string, content string) { func (s *Service) GetLogs(c *gin.Context) (int, interface{}) { var logs []Log s.Mysql.Model(&Log{}).Order("`id` DESC").Limit(30).Find(&logs) - return s.makeSuccessJSON(logs) + return utils.MakeSuccessJSON(logs) } // Panel returns the system runtime status, which is used in backstage data panel. @@ -48,11 +49,11 @@ func (s *Service) Panel(c *gin.Context) (int, interface{}) { m := new(runtime.MemStats) runtime.ReadMemStats(m) - return s.makeSuccessJSON(gin.H{ + return utils.MakeSuccessJSON(gin.H{ "SubmitFlag": submitFlag, "CheckDown": checkDown, - "NumGoroutine": runtime.NumGoroutine(), // Goroutine number - "MemAllocated": s.FileSize(int64(m.Alloc)), // Allocated memory + "NumGoroutine": runtime.NumGoroutine(), // Goroutine number + "MemAllocated": utils.FileSize(int64(m.Alloc)), // Allocated memory "TotalScore": s.TotalScore(), "PreviousRoundScore": s.PreviousRoundScore(), }) diff --git a/src/main_test.go b/src/main_test.go index f9757f4..807e7f4 100644 --- a/src/main_test.go +++ b/src/main_test.go @@ -3,12 +3,15 @@ package main import ( "github.com/gin-gonic/gin" "github.com/thanhpk/randstr" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/utils" "os" "time" ) var service *Service var managerToken string +var checkToken string var team []struct { Name string `json:"Name"` Password string `json:"Password"` @@ -19,8 +22,9 @@ var team []struct { func init() { gin.SetMode(gin.ReleaseMode) service = new(Service) - service.Conf = &Config{ - Base: Base{ + config := conf.Get() + *config = conf.Config{ + Base: conf.Base{ Title: "HCTF", BeginTime: time.Now(), RestTime: nil, @@ -33,19 +37,18 @@ func init() { CheckDownScore: 10, AttackScore: 10, }, - MySQL: MySQL{ + MySQL: conf.MySQL{ DBHost: "127.0.0.1:3306", DBUsername: "root", DBPassword: os.Getenv("TEST_DB_PASSWORD"), DBName: os.Getenv("TEST_DB_NAME"), }, } - service.initI18n() service.initMySQL() service.initStore() service.initTimer() - managerToken = service.generateToken() + managerToken = utils.GenerateToken() team = make([]struct { Name string `json:"Name"` Password string `json:"Password"` @@ -56,8 +59,9 @@ func init() { // Test manager account e99:qwe1qwe2qwe3 service.Mysql.Create(&Manager{ Name: "e99", - Password: service.addSalt("qwe1qwe2qwe3"), + Password: utils.AddSalt("qwe1qwe2qwe3"), Token: managerToken, + IsCheck: false, }) service.Router = service.initRouter() diff --git a/src/manager.go b/src/manager.go index 4b5658d..b695ff7 100644 --- a/src/manager.go +++ b/src/manager.go @@ -4,6 +4,8 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" "github.com/thanhpk/randstr" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "strconv" ) @@ -13,6 +15,7 @@ type Manager struct { Name string Password string `json:"-"` + IsCheck bool Token string // For single sign-on } @@ -27,40 +30,46 @@ func (s *Service) ManagerLogin(c *gin.Context) (int, interface{}) { var formData ManagerLoginForm err := c.BindJSON(&formData) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } var manager Manager s.Mysql.Where(&Manager{Name: formData.Name}).Find(&manager) - if manager.Name != "" && s.checkPassword(formData.Password, manager.Password) { + // The check account can't login. + if manager.ID != 0 && manager.Name != "" && utils.CheckPassword(formData.Password, manager.Password) && !manager.IsCheck { // Login successfully - token := s.generateToken() + token := utils.GenerateToken() tx := s.Mysql.Begin() if tx.Model(&Manager{}).Where(&Manager{Name: manager.Name}).Updates(&Manager{Token: token}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "general.server_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "general.server_error"), ) } tx.Commit() - return s.makeSuccessJSON(token) + return utils.MakeSuccessJSON(token) } - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "manager.login_error"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "manager.login_error"), ) } // ManagerLogout is the manager logout handler. func (s *Service) ManagerLogout(c *gin.Context) (int, interface{}) { token := c.GetHeader("Authorization") + tx := s.Mysql.Begin() if token != "" { - s.Mysql.Model(&Manager{}).Where("token = ?", token).Delete(&Token{}) + if tx.Model(&Manager{}).Where("`token` = ? AND `is_check` = ?", token, false).Update(map[string]interface{}{"token": ""}).RowsAffected != 1 { + tx.Rollback() + } else { + tx.Commit() + } } - return s.makeSuccessJSON( - s.I18n.T(c.GetString("lang"), "manager.logout_success"), + return utils.MakeSuccessJSON( + locales.I18n.T(c.GetString("lang"), "manager.logout_success"), ) } @@ -68,48 +77,56 @@ func (s *Service) ManagerLogout(c *gin.Context) (int, interface{}) { func (s *Service) GetAllManager() (int, interface{}) { var manager []Manager s.Mysql.Model(&Manager{}).Find(&manager) - return s.makeSuccessJSON(manager) + return utils.MakeSuccessJSON(manager) } // NewManager is add a new manager handler. func (s *Service) NewManager(c *gin.Context) (int, interface{}) { type InputForm struct { + IsCheck bool `json:"IsCheck"` Name string `json:"Name" binding:"required"` - Password string `json:"Password" binding:"required"` + Password string `json:"Password"` // The check account doesn't need the password. } var formData InputForm err := c.BindJSON(&formData) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), + ) + } + + if !formData.IsCheck && formData.Password == "" { + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "manager.error_payload"), ) } var checkManager Manager s.Mysql.Model(&Manager{}).Where(&Manager{Name: formData.Name}).Find(&checkManager) if checkManager.ID != 0 { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "manager.repeat"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "manager.repeat"), ) } manager := Manager{ Name: formData.Name, - Password: s.addSalt(formData.Password), + IsCheck: formData.IsCheck, + Password: utils.AddSalt(formData.Password), } tx := s.Mysql.Begin() if tx.Create(&manager).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "manager.post_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "manager.post_error"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.new_manager", gin.H{"name": manager.Name})), + string(locales.I18n.T(c.GetString("lang"), "log.new_manager", gin.H{"name": manager.Name})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "manager.post_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "manager.post_success")) } // RefreshManagerToken can refresh a manager's token. @@ -117,94 +134,94 @@ func (s *Service) NewManager(c *gin.Context) (int, interface{}) { func (s *Service) RefreshManagerToken(c *gin.Context) (int, interface{}) { idStr, ok := c.GetQuery("id") if !ok { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } id, err := strconv.Atoi(idStr) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), ) } tx := s.Mysql.Begin() - token := s.generateToken() + token := utils.GenerateToken() if tx.Model(&Manager{}).Where(&Manager{Model: gorm.Model{ID: uint(id)}}).Update(&Manager{ Token: token, }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "manager.update_token_fail"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "manager.update_token_fail"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.manager_token", gin.H{"id": id})), + string(locales.I18n.T(c.GetString("lang"), "log.manager_token", gin.H{"id": id})), ) - return s.makeSuccessJSON(token) + return utils.MakeSuccessJSON(token) } // ChangeManagerPassword will change a manager's password to a random string. func (s *Service) ChangeManagerPassword(c *gin.Context) (int, interface{}) { idStr, ok := c.GetQuery("id") if !ok { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } id, err := strconv.Atoi(idStr) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), ) } tx := s.Mysql.Begin() password := randstr.String(16) - if tx.Model(&Manager{}).Where(&Manager{Model: gorm.Model{ID: uint(id)}}).Update(&Manager{ - Password: s.addSalt(password), + if tx.Model(&Manager{}).Where(map[string]interface{}{"id": uint(id), "is_check": false}).Update(&Manager{ + Password: utils.AddSalt(password), }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "manager.update_password_fail"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "manager.update_password_fail"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.manager_password", gin.H{"id": id})), + string(locales.I18n.T(c.GetString("lang"), "log.manager_password", gin.H{"id": id})), ) - return s.makeSuccessJSON(password) + return utils.MakeSuccessJSON(password) } // DeleteManager is delete manager handler. func (s *Service) DeleteManager(c *gin.Context) (int, interface{}) { idStr, ok := c.GetQuery("id") if !ok { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } id, err := strconv.Atoi(idStr) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), ) } tx := s.Mysql.Begin() if tx.Model(&Manager{}).Where("id = ?", id).Delete(&Manager{}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "manager.delete_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "manager.delete_error"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.delete_manager", gin.H{"id": id})), + string(locales.I18n.T(c.GetString("lang"), "log.delete_manager", gin.H{"id": id})), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "manager.delete_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "manager.delete_success")) } diff --git a/src/manager_test.go b/src/manager_test.go index ae00516..1d38f5c 100644 --- a/src/manager_test.go +++ b/src/manager_test.go @@ -19,12 +19,18 @@ func TestService_ManagerLogout(t *testing.T) { func TestService_ManagerLogin(t *testing.T) { w := httptest.NewRecorder() + // JSON error + req, _ := http.NewRequest("POST", "/api/manager/login", bytes.NewBuffer([]byte(`Name=e99&Password=qwe1qwe2qwe3`))) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 400, w.Code) + // Login fail + w = httptest.NewRecorder() jsonData, _ := json.Marshal(map[string]interface{}{ "Name": "e99", "Password": "123456", }) - req, _ := http.NewRequest("POST", "/api/manager/login", bytes.NewBuffer(jsonData)) + req, _ = http.NewRequest("POST", "/api/manager/login", bytes.NewBuffer(jsonData)) service.Router.ServeHTTP(w, req) assert.Equal(t, 403, w.Code) @@ -58,12 +64,31 @@ func TestService_GetAllManager(t *testing.T) { func TestService_NewManager(t *testing.T) { w := httptest.NewRecorder() + // JSON error + req, _ := http.NewRequest("POST", "/api/manager/manager", bytes.NewBuffer([]byte(`Name=e991111&Password=1122334455`))) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 400, w.Code) + // repeat manager + w = httptest.NewRecorder() jsonData, _ := json.Marshal(map[string]interface{}{ "Name": "e99", "Password": "123456", }) - req, _ := http.NewRequest("POST", "/api/manager/manager", bytes.NewBuffer(jsonData)) + req, _ = http.NewRequest("POST", "/api/manager/manager", bytes.NewBuffer(jsonData)) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 400, w.Code) + + // Wrong check account + w = httptest.NewRecorder() + jsonData, _ = json.Marshal(map[string]interface{}{ + "Name": "e99pppp", + "Password": "", + "IsCheck": false, + }) + req, _ = http.NewRequest("POST", "/api/manager/manager", bytes.NewBuffer(jsonData)) req.Header.Set("Authorization", managerToken) service.Router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) @@ -77,6 +102,17 @@ func TestService_NewManager(t *testing.T) { req.Header.Set("Authorization", managerToken) service.Router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) + + w = httptest.NewRecorder() + jsonData, _ = json.Marshal(map[string]interface{}{ + "Name": "check", + "Password": "useless_password", + "IsCheck": true, + }) + req, _ = http.NewRequest("POST", "/api/manager/manager", bytes.NewBuffer(jsonData)) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) } func TestService_RefreshManagerToken(t *testing.T) { @@ -87,6 +123,13 @@ func TestService_RefreshManagerToken(t *testing.T) { service.Router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) + // no id + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "/api/manager/manager/token", nil) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 400, w.Code) + // id not exist w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/api/manager/manager/token?id=233", nil) @@ -100,6 +143,20 @@ func TestService_RefreshManagerToken(t *testing.T) { req.Header.Set("Authorization", managerToken) service.Router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) + + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "/api/manager/manager/token?id=3", nil) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + var backJSON = struct { + Error int `json:"error"` + Msg string `json:"msg"` + Data string `json:"data"` + }{} + err := json.Unmarshal(w.Body.Bytes(), &backJSON) + assert.Equal(t, nil, err) + assert.Equal(t, 200, w.Code) + checkToken = backJSON.Data } func TestService_ChangeManagerPassword(t *testing.T) { @@ -110,6 +167,13 @@ func TestService_ChangeManagerPassword(t *testing.T) { service.Router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) + // no id + w = httptest.NewRecorder() + req, _ = http.NewRequest("GET", "/api/manager/manager/changePassword", nil) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 400, w.Code) + // id not exist w = httptest.NewRecorder() req, _ = http.NewRequest("GET", "/api/manager/manager/changePassword?id=233", nil) @@ -133,6 +197,13 @@ func TestService_DeleteManager(t *testing.T) { service.Router.ServeHTTP(w, req) assert.Equal(t, 400, w.Code) + // no id exist + w = httptest.NewRecorder() + req, _ = http.NewRequest("DELETE", "/api/manager/manager", nil) + req.Header.Set("Authorization", managerToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 400, w.Code) + // id not exist w = httptest.NewRecorder() req, _ = http.NewRequest("DELETE", "/api/manager/manager?id=233", nil) @@ -147,3 +218,11 @@ func TestService_DeleteManager(t *testing.T) { service.Router.ServeHTTP(w, req) assert.Equal(t, 200, w.Code) } + +func TestService_ManagerRequired(t *testing.T) { + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/api/manager/teams", nil) + req.Header.Set("Authorization", checkToken) + service.Router.ServeHTTP(w, req) + assert.Equal(t, 401, w.Code) +} diff --git a/src/mysql.go b/src/mysql.go index 0fa7e42..6e362dc 100644 --- a/src/mysql.go +++ b/src/mysql.go @@ -4,16 +4,21 @@ import ( "fmt" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" + "github.com/vidar-team/Cardinal/src/conf" "log" ) func (s *Service) initMySQL() { db, err := gorm.Open("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&loc=Local&charset=utf8mb4,utf8", - s.Conf.MySQL.DBUsername, - s.Conf.MySQL.DBPassword, - s.Conf.MySQL.DBHost, - s.Conf.MySQL.DBName, + conf.Get().DBUsername, + conf.Get().DBPassword, + conf.Get().DBHost, + conf.Get().DBName, )) + + db.DB().SetMaxIdleConns(128) + db.DB().SetMaxOpenConns(256) + if err != nil { log.Fatalln(err) } diff --git a/src/rank.go b/src/rank.go index d5c5fdb..39dbfab 100644 --- a/src/rank.go +++ b/src/rank.go @@ -2,6 +2,8 @@ package main import ( "github.com/patrickmn/go-cache" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/locales" ) // RankItem is used to create the ranking list. @@ -69,7 +71,7 @@ func (s *Service) SetRankListTitle() { } s.Store.Set("rankListTitle", visibleChallengeTitle, cache.NoExpiration) // Save challenge title into cache. - s.NewLog(NORMAL, "system", string(s.I18n.T(s.Conf.Base.SystemLanguage, "log.rank_list_success"))) + s.NewLog(NORMAL, "system", string(locales.I18n.T(conf.Get().SystemLanguage, "log.rank_list_success"))) } // SetRankList will calculate the ranking list. diff --git a/src/router.go b/src/router.go index ae1644c..91669b7 100644 --- a/src/router.go +++ b/src/router.go @@ -4,6 +4,10 @@ import ( "github.com/gin-contrib/cors" "github.com/gin-contrib/static" "github.com/gin-gonic/gin" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/frontend" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" ) func (s *Service) initRouter() *gin.Engine { @@ -15,19 +19,19 @@ func (s *Service) initRouter() *gin.Engine { })) api := r.Group("/api") - api.Use(s.I18nMiddleware()) + api.Use(locales.Middleware()) // Frontend - r.Use(static.Serve("/", frontendFS())) + r.Use(static.Serve("/", frontend.FS())) // Cardinal basic info api.Any("/", func(c *gin.Context) { - c.JSON(s.makeSuccessJSON("Cardinal")) + c.JSON(utils.MakeSuccessJSON("Cardinal")) }) api.GET("/base", func(c *gin.Context) { - c.JSON(s.makeSuccessJSON(gin.H{ - "Title": s.Conf.Title, + c.JSON(utils.MakeSuccessJSON(gin.H{ + "Title": conf.Get().Title, })) }) api.GET("/time", func(c *gin.Context) { @@ -62,7 +66,7 @@ func (s *Service) initRouter() *gin.Engine { c.JSON(s.GetSelfGameBoxes(c)) }) team.GET("/rank", func(c *gin.Context) { - c.JSON(s.makeSuccessJSON(gin.H{"Title": s.GetRankListTitle(), "Rank": s.GetRankList()})) + c.JSON(utils.MakeSuccessJSON(gin.H{"Title": s.GetRankListTitle(), "Rank": s.GetRankList()})) }) team.GET("/bulletins", func(c *gin.Context) { c.JSON(s.GetAllBulletins()) @@ -80,8 +84,8 @@ func (s *Service) initRouter() *gin.Engine { }) // For manager - manager := api.Group("/manager") - manager.Use(s.ManagerAuthRequired()) + check := api.Group("/manager").Use(s.AdminAuthRequired()) + manager := api.Group("/manager").Use(s.AdminAuthRequired(), s.ManagerRequired()) { // Challenge manager.GET("/challenges", func(c *gin.Context) { @@ -110,6 +114,9 @@ func (s *Service) initRouter() *gin.Engine { manager.PUT("/gamebox", func(c *gin.Context) { c.JSON(s.EditGameBox(c)) }) + manager.GET("/gameboxes/sshTest", func(c *gin.Context) { + c.JSON(s.testSSH(c)) + }) // Team manager.GET("/teams", func(c *gin.Context) { @@ -152,9 +159,12 @@ func (s *Service) initRouter() *gin.Engine { manager.POST("/flag/generate", func(c *gin.Context) { c.JSON(s.GenerateFlag(c)) }) + manager.GET("/flag/export", func(c *gin.Context) { + c.JSON(s.ExportFlag(c)) + }) // Check - manager.POST("/checkDown", func(c *gin.Context) { + check.POST("/checkDown", func(c *gin.Context) { c.JSON(s.CheckDown(c)) }) @@ -182,7 +192,7 @@ func (s *Service) initRouter() *gin.Engine { c.JSON(s.GetLogs(c)) }) manager.GET("/rank", func(c *gin.Context) { - c.JSON(s.makeSuccessJSON(gin.H{"Title": s.GetRankListTitle(), "Rank": s.GetManagerRankList()})) + c.JSON(utils.MakeSuccessJSON(gin.H{"Title": s.GetRankListTitle(), "Rank": s.GetManagerRankList()})) }) manager.GET("/panel", func(c *gin.Context) { c.JSON(s.Panel(c)) @@ -191,15 +201,15 @@ func (s *Service) initRouter() *gin.Engine { // 404 r.NoRoute(func(c *gin.Context) { - c.JSON(s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "general.not_found"), + c.JSON(utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "general.not_found"), )) }) // 405 r.NoMethod(func(c *gin.Context) { - c.JSON(s.makeErrJSON(405, 40500, - s.I18n.T(c.GetString("lang"), "general.method_not_allow"), + c.JSON(utils.MakeErrJSON(405, 40500, + locales.I18n.T(c.GetString("lang"), "general.method_not_allow"), )) }) @@ -211,8 +221,8 @@ func (s *Service) TeamAuthRequired() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { - c.JSON(s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.no_auth"), + c.JSON(utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.no_auth"), )) c.Abort() return @@ -221,8 +231,8 @@ func (s *Service) TeamAuthRequired() gin.HandlerFunc { var tokenData Token s.Mysql.Where(&Token{Token: token}).Find(&tokenData) if tokenData.ID == 0 { - c.JSON(s.makeErrJSON(401, 40100, - s.I18n.T(c.GetString("lang"), "general.no_auth"), + c.JSON(utils.MakeErrJSON(401, 40100, + locales.I18n.T(c.GetString("lang"), "general.no_auth"), )) c.Abort() return @@ -233,13 +243,13 @@ func (s *Service) TeamAuthRequired() gin.HandlerFunc { } } -// ManagerAuthRequired is the manager permission check middleware. -func (s *Service) ManagerAuthRequired() gin.HandlerFunc { +// AdminAuthRequired is the admin permission check middleware. +func (s *Service) AdminAuthRequired() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("Authorization") if token == "" { - c.JSON(s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "general.no_auth"), + c.JSON(utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "general.no_auth"), )) c.Abort() return @@ -248,14 +258,29 @@ func (s *Service) ManagerAuthRequired() gin.HandlerFunc { var managerData Manager s.Mysql.Where(&Manager{Token: token}).Find(&managerData) if managerData.ID == 0 { - c.JSON(s.makeErrJSON(401, 40100, - s.I18n.T(c.GetString("lang"), "general.no_auth"), + c.JSON(utils.MakeErrJSON(401, 40100, + locales.I18n.T(c.GetString("lang"), "general.no_auth"), )) c.Abort() return } c.Set("managerData", managerData) + c.Set("isCheck", managerData.IsCheck) + c.Next() + } +} + +// ManagerRequired make sure the account is the manager. +func (s *Service) ManagerRequired() gin.HandlerFunc { + return func(c *gin.Context) { + if c.GetBool("isCheck") { + c.JSON(utils.MakeErrJSON(401, 40100, + locales.I18n.T(c.GetString("lang"), "manager.manager_required"), + )) + c.Abort() + return + } c.Next() } } diff --git a/src/score.go b/src/score.go index 5b5666c..c1329d5 100644 --- a/src/score.go +++ b/src/score.go @@ -3,6 +3,8 @@ package main import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/locales" "time" ) @@ -44,7 +46,7 @@ func (s *Service) CalculateRoundScore(round int) { endTime := time.Now().UnixNano() s.NewLog(WARNING, "system", string( - s.I18n.T(s.Conf.Base.SystemLanguage, "log.score_success", + locales.I18n.T(conf.Get().SystemLanguage, "log.score_success", gin.H{ "round": round, "time": float64(endTime-startTime) / float64(time.Second), @@ -90,7 +92,7 @@ func (s *Service) AddAttack(round int) { var attackActions []AttackAction s.Mysql.Model(&AttackAction{}).Where(&AttackAction{GameBoxID: gameBox.ID, Round: round}).Find(&attackActions) if len(attackActions) != 0 { - score := float64(s.Conf.AttackScore) / float64(len(attackActions)) // Score which every attacker can get from this gamebox. + score := float64(conf.Get().AttackScore) / float64(len(attackActions)) // Score which every attacker can get from this gamebox. // Add score to the attackers. for _, action := range attackActions { // Get the attacker's gamebox ID of this challenge. @@ -125,7 +127,7 @@ func (s *Service) MinusAttack(round int) { GameBoxID: action.GameBoxID, Round: round, Reason: "been_attacked", - Score: float64(-s.Conf.AttackScore), + Score: float64(-conf.Get().AttackScore), }) } } @@ -142,7 +144,7 @@ func (s *Service) MinusCheckDown(round int) { GameBoxID: action.GameBoxID, Round: round, Reason: "checkdown", - Score: float64(-s.Conf.CheckDownScore), + Score: float64(-conf.Get().CheckDownScore), }) } } @@ -156,7 +158,7 @@ func (s *Service) AddCheckDown(round int) { // Get the check down teams of this challenge. var downActions []DownAction s.Mysql.Model(&DownAction{}).Where(&DownAction{ChallengeID: challenge.ID, Round: round}).Find(&downActions) - totalScore := len(downActions) * s.Conf.CheckDownScore // Score which every online team can get from this challenge. + totalScore := len(downActions) * conf.Get().CheckDownScore // Score which every online team can get from this challenge. // Get the service online teams' Gamebox ID of this challenge. // For the score will be added separately into their **gameboxes**. diff --git a/src/service.go b/src/service.go index ef3ee52..3517b30 100644 --- a/src/service.go +++ b/src/service.go @@ -4,28 +4,24 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" "github.com/patrickmn/go-cache" - "github.com/qor/i18n" + "github.com/vidar-team/Cardinal/src/conf" ) // Service is the main struct contains all the part of the Cardinal. type Service struct { - Conf *Config Mysql *gorm.DB Timer *Timer - I18n *i18n.I18n Store *cache.Cache Router *gin.Engine } func (s *Service) init() { - s.initI18n() s.install() - s.initConfig() s.initMySQL() s.initManager() s.initStore() s.initTimer() s.Router = s.initRouter() - panic(s.Router.Run(s.Conf.Base.Port)) + panic(s.Router.Run(conf.Get().Port)) } diff --git a/src/team.go b/src/team.go index 6c752eb..6c6db29 100644 --- a/src/team.go +++ b/src/team.go @@ -4,6 +4,8 @@ import ( "github.com/gin-gonic/gin" "github.com/jinzhu/gorm" "github.com/thanhpk/randstr" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "strconv" ) @@ -37,29 +39,29 @@ func (s *Service) TeamLogin(c *gin.Context) (int, interface{}) { var formData TeamLoginForm err := c.BindJSON(&formData) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } var team Team s.Mysql.Where(&Team{Name: formData.Name}).Find(&team) - if team.Name != "" && s.checkPassword(formData.Password, team.Password) { + if team.Name != "" && utils.CheckPassword(formData.Password, team.Password) { // Login successfully - token := s.generateToken() + token := utils.GenerateToken() tx := s.Mysql.Begin() if tx.Create(&Token{TeamID: team.ID, Token: token}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "general.server_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "general.server_error"), ) } tx.Commit() - return s.makeSuccessJSON(token) + return utils.MakeSuccessJSON(token) } - return s.makeErrJSON(403, 40300, - s.I18n.T(c.GetString("lang"), "team.login_error"), + return utils.MakeErrJSON(403, 40300, + locales.I18n.T(c.GetString("lang"), "team.login_error"), ) } @@ -69,15 +71,15 @@ func (s *Service) TeamLogout(c *gin.Context) (int, interface{}) { if token != "" { s.Mysql.Model(&Token{}).Where("token = ?", token).Delete(&Token{}) } - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "team.logout_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "team.logout_success")) } // GetTeamInfo returns the team its info. func (s *Service) GetTeamInfo(c *gin.Context) (int, interface{}) { teamID, ok := c.Get("teamID") if !ok { - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "general.server_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "general.server_error"), ) } @@ -95,7 +97,7 @@ func (s *Service) GetTeamInfo(c *gin.Context) (int, interface{}) { } } - return s.makeSuccessJSON(gin.H{ + return utils.MakeSuccessJSON(gin.H{ "Name": teamInfo.Name, "Logo": teamInfo.Logo, "Score": teamInfo.Score, @@ -108,7 +110,7 @@ func (s *Service) GetTeamInfo(c *gin.Context) (int, interface{}) { func (s *Service) GetAllTeams() (int, interface{}) { var teams []Team s.Mysql.Model(&Team{}).Find(&teams) - return s.makeSuccessJSON(teams) + return utils.MakeSuccessJSON(teams) } // NewTeams is add new team(s) handler. @@ -120,8 +122,8 @@ func (s *Service) NewTeams(c *gin.Context) (int, interface{}) { var inputForm []InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } @@ -134,20 +136,20 @@ func (s *Service) NewTeams(c *gin.Context) (int, interface{}) { var count int s.Mysql.Model(Team{}).Where(&Team{Name: item.Name}).Count(&count) if count != 0 { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "team.repeat"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "team.repeat"), ) } // Team name can't be empty. if item.Name == "" { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "team.team_name_empty"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "team.team_name_empty"), ) } } if len(tmpTeamName) != len(inputForm) { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "team.repeat"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "team.repeat"), ) } @@ -163,14 +165,14 @@ func (s *Service) NewTeams(c *gin.Context) (int, interface{}) { password := randstr.String(16) newTeam := &Team{ Name: item.Name, - Password: s.addSalt(password), + Password: utils.AddSalt(password), Logo: item.Logo, SecretKey: randstr.Hex(16), } if tx.Create(newTeam).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50000, - s.I18n.T(c.GetString("lang"), "team.post_error"), + return utils.MakeErrJSON(500, 50000, + locales.I18n.T(c.GetString("lang"), "team.post_error"), ) } resultData = append(resultData, resultItem{ @@ -182,12 +184,12 @@ func (s *Service) NewTeams(c *gin.Context) (int, interface{}) { tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.new_team", gin.H{ + string(locales.I18n.T(c.GetString("lang"), "log.new_team", gin.H{ "count": len(inputForm), "teamName": teamName, })), ) - return s.makeSuccessJSON(resultData) + return utils.MakeSuccessJSON(resultData) } // EditTeam is edit a team info handler. @@ -200,8 +202,8 @@ func (s *Service) EditTeam(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } @@ -209,8 +211,8 @@ func (s *Service) EditTeam(c *gin.Context) (int, interface{}) { var count int s.Mysql.Model(Team{}).Where(&Team{Model: gorm.Model{ID: inputForm.ID}}).Count(&count) if count == 0 { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "team.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "team.not_found"), ) } @@ -218,8 +220,8 @@ func (s *Service) EditTeam(c *gin.Context) (int, interface{}) { var repeatCheckTeam Team s.Mysql.Model(Team{}).Where(&Team{Name: inputForm.Name}).Find(&repeatCheckTeam) if repeatCheckTeam.Name != "" && repeatCheckTeam.ID != inputForm.ID { - return s.makeErrJSON(400, 40001, - s.I18n.T(c.GetString("lang"), "team.repeat"), + return utils.MakeErrJSON(400, 40001, + locales.I18n.T(c.GetString("lang"), "team.repeat"), ) } @@ -229,53 +231,53 @@ func (s *Service) EditTeam(c *gin.Context) (int, interface{}) { "Logo": inputForm.Logo, }).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50001, - s.I18n.T(c.GetString("lang"), "team.put_error"), + return utils.MakeErrJSON(500, 50001, + locales.I18n.T(c.GetString("lang"), "team.put_error"), ) } tx.Commit() - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "team.put_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "team.put_success")) } // DeleteTeam is delete a team handler. func (s *Service) DeleteTeam(c *gin.Context) (int, interface{}) { idStr, ok := c.GetQuery("id") if !ok { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_query"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_query"), ) } id, err := strconv.Atoi(idStr) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.must_be_number", gin.H{"key": "id"}), ) } var team Team s.Mysql.Where(&Team{Model: gorm.Model{ID: uint(id)}}).Find(&team) if team.Name == "" { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "team.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "team.not_found"), ) } tx := s.Mysql.Begin() if tx.Where("id = ?", uint(id)).Delete(&Team{}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50002, - s.I18n.T(c.GetString("lang"), "team.delete_error"), + return utils.MakeErrJSON(500, 50002, + locales.I18n.T(c.GetString("lang"), "team.delete_error"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.delete_team", gin.H{ + string(locales.I18n.T(c.GetString("lang"), "log.delete_team", gin.H{ "teamName": team.Name, })), ) - return s.makeSuccessJSON(s.I18n.T(c.GetString("lang"), "team.delete_success")) + return utils.MakeSuccessJSON(locales.I18n.T(c.GetString("lang"), "team.delete_success")) } // ResetTeamPassword will reset a team's password. The new password is a random string. @@ -286,8 +288,8 @@ func (s *Service) ResetTeamPassword(c *gin.Context) (int, interface{}) { var inputForm InputForm err := c.BindJSON(&inputForm) if err != nil { - return s.makeErrJSON(400, 40000, - s.I18n.T(c.GetString("lang"), "general.error_payload"), + return utils.MakeErrJSON(400, 40000, + locales.I18n.T(c.GetString("lang"), "general.error_payload"), ) } @@ -295,25 +297,25 @@ func (s *Service) ResetTeamPassword(c *gin.Context) (int, interface{}) { var checkTeam Team s.Mysql.Model(Team{}).Where(&Team{Model: gorm.Model{ID: inputForm.ID}}).Find(&checkTeam) if checkTeam.Name == "" { - return s.makeErrJSON(404, 40400, - s.I18n.T(c.GetString("lang"), "team.not_found"), + return utils.MakeErrJSON(404, 40400, + locales.I18n.T(c.GetString("lang"), "team.not_found"), ) } newPassword := randstr.String(16) tx := s.Mysql.Begin() - if tx.Model(&Team{}).Where(&Team{Model: gorm.Model{ID: inputForm.ID}}).Updates(&Team{Password: s.addSalt(newPassword)}).RowsAffected != 1 { + if tx.Model(&Team{}).Where(&Team{Model: gorm.Model{ID: inputForm.ID}}).Updates(&Team{Password: utils.AddSalt(newPassword)}).RowsAffected != 1 { tx.Rollback() - return s.makeErrJSON(500, 50001, - s.I18n.T(c.GetString("lang"), "team.reset_password_error"), + return utils.MakeErrJSON(500, 50001, + locales.I18n.T(c.GetString("lang"), "team.reset_password_error"), ) } tx.Commit() s.NewLog(NORMAL, "manager_operate", - string(s.I18n.T(c.GetString("lang"), "log.team_reset_password", gin.H{ + string(locales.I18n.T(c.GetString("lang"), "log.team_reset_password", gin.H{ "teamName": checkTeam.Name, })), ) - return s.makeSuccessJSON(newPassword) + return utils.MakeSuccessJSON(newPassword) } diff --git a/src/timer.go b/src/timer.go index 1734f29..338e56c 100644 --- a/src/timer.go +++ b/src/timer.go @@ -3,6 +3,9 @@ package main import ( "fmt" "github.com/gin-gonic/gin" + "github.com/vidar-team/Cardinal/src/conf" + "github.com/vidar-team/Cardinal/src/locales" + "github.com/vidar-team/Cardinal/src/utils" "log" "math" "time" @@ -22,7 +25,7 @@ type Timer struct { } func (s *Service) getTime() (int, interface{}) { - return s.makeSuccessJSON(gin.H{ + return utils.MakeSuccessJSON(gin.H{ "BeginTime": s.Timer.BeginTime.Unix(), "EndTime": s.Timer.EndTime.Unix(), "Duration": s.Timer.Duration, @@ -34,10 +37,10 @@ func (s *Service) getTime() (int, interface{}) { func (s *Service) initTimer() { s.Timer = &Timer{ - BeginTime: s.Conf.Base.BeginTime, - EndTime: s.Conf.Base.EndTime, - Duration: s.Conf.Base.Duration, - RestTime: s.Conf.Base.RestTime, + BeginTime: conf.Get().BeginTime, + EndTime: conf.Get().EndTime, + Duration: conf.Get().Duration, + RestTime: conf.Get().RestTime, NowRound: -1, } s.checkTimeConfig() @@ -83,11 +86,11 @@ func (s *Service) initTimer() { } s.Timer.TotalRound = int(totalTime / 60 / int64(s.Timer.Duration)) - log.Println(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.total_round", gin.H{ + log.Println(locales.I18n.T(conf.Get().SystemLanguage, "timer.total_round", gin.H{ "round": s.Timer.TotalRound, })) - log.Println(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.total_time", gin.H{ + log.Println(locales.I18n.T(conf.Get().SystemLanguage, "timer.total_time", gin.H{ "time": int(totalTime / 60), })) @@ -155,6 +158,9 @@ func (s *Service) timerProcess() { go s.CalculateRoundScore(s.Timer.NowRound - 1) } + // Auto refresh flag + go s.refreshFlag() + fmt.Println(s.Timer.NowRound) } } @@ -168,7 +174,7 @@ func (s *Service) timerProcess() { if !lastRoundCalculate { lastRoundCalculate = true go s.CalculateRoundScore(s.Timer.TotalRound) - s.NewLog(IMPORTANT, "system", string(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.end"))) + s.NewLog(IMPORTANT, "system", string(locales.I18n.T(conf.Get().SystemLanguage, "timer.end"))) } s.Timer.Status = "end" @@ -180,16 +186,16 @@ func (s *Service) timerProcess() { func (s *Service) checkTimeConfig() { if s.Timer.BeginTime.Unix() > s.Timer.EndTime.Unix() { - log.Fatalln(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.start_time_error")) + log.Fatalln(locales.I18n.T(conf.Get().SystemLanguage, "timer.start_time_error")) } // Check the RestTime in config file is correct. for key, dur := range s.Timer.RestTime { if len(dur) != 2 { - log.Fatalln(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.single_rest_time_error")) + log.Fatalln(locales.I18n.T(conf.Get().SystemLanguage, "timer.single_rest_time_error")) } if dur[0].Unix() >= dur[1].Unix() { - log.Fatalln(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.rest_time_start_error", + log.Fatalln(locales.I18n.T(conf.Get().SystemLanguage, "timer.rest_time_start_error", gin.H{ "from": dur[0].String(), "to": dur[1].String(), @@ -197,7 +203,7 @@ func (s *Service) checkTimeConfig() { )) } if dur[0].Unix() <= s.Timer.BeginTime.Unix() || dur[1].Unix() >= s.Timer.EndTime.Unix() { - log.Fatalln(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.rest_time_overflow_error", + log.Fatalln(locales.I18n.T(conf.Get().SystemLanguage, "timer.rest_time_overflow_error", gin.H{ "from": dur[0].String(), "to": dur[1].String(), @@ -206,7 +212,7 @@ func (s *Service) checkTimeConfig() { } // RestTime should in order. if key != 0 && dur[0].Unix() <= s.Timer.RestTime[key-1][0].Unix() { - log.Fatalln(s.I18n.T(s.Conf.Base.SystemLanguage, "timer.rest_time_order_error", + log.Fatalln(locales.I18n.T(conf.Get().SystemLanguage, "timer.rest_time_order_error", gin.H{ "from": dur[0].String(), "to": dur[1].String(), diff --git a/src/utils.go b/src/utils.go deleted file mode 100644 index 5b55123..0000000 --- a/src/utils.go +++ /dev/null @@ -1,78 +0,0 @@ -package main - -import ( - "bufio" - "crypto/hmac" - "crypto/sha1" - "fmt" - "github.com/dustin/go-humanize" - "github.com/gin-gonic/gin" - "github.com/satori/go.uuid" - "io" - "os" - "strings" -) - -func (s *Service) makeErrJSON(httpStatusCode int, errCode int, msg interface{}) (int, interface{}) { - return httpStatusCode, gin.H{"error": errCode, "msg": fmt.Sprint(msg)} -} - -func (s *Service) makeSuccessJSON(data interface{}) (int, interface{}) { - return 200, gin.H{"error": 0, "msg": "success", "data": data} -} - -func (s *Service) checkPassword(inputPassword string, realPassword string) bool { - // sha1( sha1(password) + salt ) - return s.addSalt(inputPassword) == realPassword -} - -func (s *Service) addSalt(input string) string { - return s.sha1Encode(s.sha1Encode(input) + s.Conf.Base.Salt) -} - -func (s *Service) generateToken() string { - return uuid.NewV4().String() -} - -func (s *Service) sha1Encode(input string) string { - h := sha1.New() - h.Write([]byte(input)) - bs := h.Sum(nil) - return fmt.Sprintf("%x", bs) -} - -func (s *Service) hmacSha1Encode(input string, key string) string { - h := hmac.New(sha1.New, []byte(key)) - _, _ = io.WriteString(h, input) - return fmt.Sprintf("%x", h.Sum(nil)) -} - -// FileSize returns the formatter text of the giving size. -func (s *Service) FileSize(size int64) string { - return humanize.IBytes(uint64(size)) -} - -// IsExist check the file or folder existed. -func IsExist(path string) bool { - _, err := os.Stat(path) - return err == nil || os.IsExist(err) -} - -// InputString used in the install.go for the config file guide. -func InputString(str *string, hint string) { - var err error - var input string - for input == "" { - fmt.Println(">", hint) - - stdin := bufio.NewReader(os.Stdin) - input, err = stdin.ReadString('\n') - input = strings.Trim(input, "\r\n") - if err != nil || input == "" { - if *str != "" { - break - } - } - *str = input - } -} diff --git a/src/utils/utils.go b/src/utils/utils.go new file mode 100644 index 0000000..c874bc3 --- /dev/null +++ b/src/utils/utils.go @@ -0,0 +1,112 @@ +package utils + +import ( + "bufio" + "crypto/hmac" + "crypto/sha1" + "fmt" + "github.com/dustin/go-humanize" + "github.com/gin-gonic/gin" + "github.com/satori/go.uuid" + "github.com/vidar-team/Cardinal/src/conf" + "golang.org/x/crypto/ssh" + "io" + "os" + "strings" + "time" +) + +// MakeErrJSON makes the error response JSON for gin. +func MakeErrJSON(httpStatusCode int, errCode int, msg interface{}) (int, interface{}) { + return httpStatusCode, gin.H{"error": errCode, "msg": fmt.Sprint(msg)} +} + +// MakeErrJSON makes the successful response JSON for gin. +func MakeSuccessJSON(data interface{}) (int, interface{}) { + return 200, gin.H{"error": 0, "msg": "success", "data": data} +} + +// CheckPassword: Add salt and check the password. +func CheckPassword(inputPassword string, realPassword string) bool { + // sha1( sha1(password) + salt ) + return HmacSha1Encode(inputPassword, conf.Get().Salt) == realPassword +} + +// Sha1Encode: Sha1 encode input string. +func Sha1Encode(input string) string { + h := sha1.New() + h.Write([]byte(input)) + bs := h.Sum(nil) + return fmt.Sprintf("%x", bs) +} + +// AddSalt: Use the config salt as key to HmacSha1Encode. +func AddSalt(input string) string { + return HmacSha1Encode(input, conf.Get().Salt) +} + +// HmacSha1Encode: HMAC SHA1 encode +func HmacSha1Encode(input string, key string) string { + h := hmac.New(sha1.New, []byte(key)) + _, _ = io.WriteString(h, input) + return fmt.Sprintf("%x", h.Sum(nil)) +} + +// GenerateToken: return UUID v4 string. +func GenerateToken() string { + return uuid.NewV4().String() +} + +// FileSize returns the formatter text of the giving size. +func FileSize(size int64) string { + return humanize.IBytes(uint64(size)) +} + +// FileIsExist check the file or folder existed. +func FileIsExist(path string) bool { + _, err := os.Stat(path) + return err == nil || os.IsExist(err) +} + +// InputString used in the install.go for the config file guide. +func InputString(str *string, hint string) { + var err error + var input string + for input == "" { + fmt.Println(">", hint) + + stdin := bufio.NewReader(os.Stdin) + input, err = stdin.ReadString('\n') + input = strings.Trim(input, "\r\n") + if err != nil || input == "" { + if *str != "" { + break + } + } + *str = input + } +} + +func SSHExecute(ip string, port string, user string, password string, command string) error { + client, err := ssh.Dial("tcp", ip+":"+port, &ssh.ClientConfig{ + User: user, + Auth: []ssh.AuthMethod{ssh.Password(password)}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + Timeout: 5 * time.Second, + }) + if err != nil { + return err + } + + session, err := client.NewSession() + if err != nil { + return err + } + defer session.Close() + err = session.Run(command) + if err != nil { + return err + } + + return nil +}