From 3226a3a58ec89c3a2eb1f2c832c782bf6a4ae142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 8 Jan 2015 09:00:00 +0100 Subject: [PATCH] Fixed hashing of passwords, Closes #3 --- grafana | 2 +- pkg/api/account.go | 4 +- pkg/api/dashboard.go | 48 ++--------------------- pkg/api/dataproxy.go | 6 +-- pkg/api/login.go | 9 +++-- pkg/api/register.go | 6 ++- pkg/api/render.go | 4 +- pkg/api/search.go | 45 ++++++++++++++++++++++ pkg/models/account.go | 2 + pkg/stores/sqlstore/accounts.go | 1 + pkg/util/encoding.go | 67 +++++++++++++++++++++++++++++++++ pkg/{utils => util}/json.go | 2 +- pkg/{utils => util}/url.go | 2 +- 13 files changed, 138 insertions(+), 60 deletions(-) create mode 100644 pkg/api/search.go create mode 100644 pkg/util/encoding.go rename pkg/{utils => util}/json.go (72%) rename pkg/{utils => util}/url.go (97%) diff --git a/grafana b/grafana index d3e11cabd51..164435f71d3 160000 --- a/grafana +++ b/grafana @@ -1 +1 @@ -Subproject commit d3e11cabd51d082244f83258b02fd635cc780c08 +Subproject commit 164435f71d3462fa1719791e1c8eb9841374d299 diff --git a/pkg/api/account.go b/pkg/api/account.go index 1972781ed52..778bda40fc4 100644 --- a/pkg/api/account.go +++ b/pkg/api/account.go @@ -4,7 +4,7 @@ import ( "github.com/torkelo/grafana-pro/pkg/bus" "github.com/torkelo/grafana-pro/pkg/middleware" m "github.com/torkelo/grafana-pro/pkg/models" - "github.com/torkelo/grafana-pro/pkg/utils" + "github.com/torkelo/grafana-pro/pkg/util" ) func GetAccount(c *middleware.Context) { @@ -59,7 +59,7 @@ func GetOtherAccounts(c *middleware.Context) { err := bus.Dispatch(&query) if err != nil { - c.JSON(500, utils.DynMap{"message": err.Error()}) + c.JSON(500, util.DynMap{"message": err.Error()}) return } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 9746f4e7c12..48689b513a2 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -1,13 +1,10 @@ package api import ( - "regexp" - "strings" - "github.com/torkelo/grafana-pro/pkg/bus" "github.com/torkelo/grafana-pro/pkg/middleware" m "github.com/torkelo/grafana-pro/pkg/models" - "github.com/torkelo/grafana-pro/pkg/utils" + "github.com/torkelo/grafana-pro/pkg/util" ) func GetDashboard(c *middleware.Context) { @@ -29,15 +26,13 @@ func DeleteDashboard(c *middleware.Context) { slug := c.Params(":slug") query := m.GetDashboardQuery{Slug: slug, AccountId: c.GetAccountId()} - err := bus.Dispatch(&query) - if err != nil { + if err := bus.Dispatch(&query); err != nil { c.JsonApiErr(404, "Dashboard not found", nil) return } cmd := m.DeleteDashboardCommand{Slug: slug, AccountId: c.GetAccountId()} - err = bus.Dispatch(&cmd) - if err != nil { + if err := bus.Dispatch(&cmd); err != nil { c.JsonApiErr(500, "Failed to delete dashboard", err) return } @@ -47,41 +42,6 @@ func DeleteDashboard(c *middleware.Context) { c.JSON(200, resp) } -func Search(c *middleware.Context) { - queryText := c.Query("q") - result := m.SearchResult{ - Dashboards: []*m.DashboardSearchHit{}, - Tags: []*m.DashboardTagCloudItem{}, - } - - if strings.HasPrefix(queryText, "tags!:") { - query := m.GetDashboardTagsQuery{} - err := bus.Dispatch(&query) - if err != nil { - c.JsonApiErr(500, "Failed to get tags from database", err) - return - } - result.Tags = query.Result - result.TagsOnly = true - } else { - searchQueryRegEx, _ := regexp.Compile(`(tags:(\w*)\sAND\s)?(?:title:)?(.*)?`) - matches := searchQueryRegEx.FindStringSubmatch(queryText) - query := m.SearchDashboardsQuery{ - Title: matches[3], - Tag: matches[2], - AccountId: c.GetAccountId(), - } - err := bus.Dispatch(&query) - if err != nil { - c.JsonApiErr(500, "Search failed", err) - return - } - result.Dashboards = query.Result - } - - c.JSON(200, result) -} - func PostDashboard(c *middleware.Context) { var cmd m.SaveDashboardCommand @@ -102,5 +62,5 @@ func PostDashboard(c *middleware.Context) { return } - c.JSON(200, utils.DynMap{"status": "success", "slug": cmd.Result.Slug}) + c.JSON(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug}) } diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 3d0c0a53c54..e634f76c598 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -8,7 +8,7 @@ import ( "github.com/torkelo/grafana-pro/pkg/bus" "github.com/torkelo/grafana-pro/pkg/middleware" m "github.com/torkelo/grafana-pro/pkg/models" - "github.com/torkelo/grafana-pro/pkg/utils" + "github.com/torkelo/grafana-pro/pkg/util" ) func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy { @@ -21,12 +21,12 @@ func NewReverseProxy(ds *m.DataSource, proxyPath string) *httputil.ReverseProxy reqQueryVals := req.URL.Query() if ds.Type == m.DS_INFLUXDB { - req.URL.Path = utils.JoinUrlFragments(target.Path, "db/"+ds.Database+"/"+proxyPath) + req.URL.Path = util.JoinUrlFragments(target.Path, "db/"+ds.Database+"/"+proxyPath) reqQueryVals.Add("u", ds.User) reqQueryVals.Add("p", ds.Password) req.URL.RawQuery = reqQueryVals.Encode() } else { - req.URL.Path = utils.JoinUrlFragments(target.Path, proxyPath) + req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath) } } diff --git a/pkg/api/login.go b/pkg/api/login.go index d1dcba14484..23598ecc117 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -6,7 +6,7 @@ import ( "github.com/torkelo/grafana-pro/pkg/log" "github.com/torkelo/grafana-pro/pkg/middleware" m "github.com/torkelo/grafana-pro/pkg/models" - "github.com/torkelo/grafana-pro/pkg/utils" + "github.com/torkelo/grafana-pro/pkg/util" ) type loginJsonModel struct { @@ -19,7 +19,7 @@ func LoginPost(c *middleware.Context) { var loginModel loginJsonModel if !c.JsonBody(&loginModel) { - c.JSON(400, utils.DynMap{"status": "bad request"}) + c.JSON(400, util.DynMap{"message": "bad request"}) return } @@ -33,7 +33,8 @@ func LoginPost(c *middleware.Context) { account := userQuery.Result - if loginModel.Password != account.Password { + passwordHashed := util.EncodePassword(loginModel.Password, account.Salt) + if passwordHashed != account.Password { c.JsonApiErr(401, "Invalid username or password", err) return } @@ -57,5 +58,5 @@ func loginUserWithAccount(account *m.Account, c *middleware.Context) { func LogoutPost(c *middleware.Context) { c.Session.Delete("accountId") - c.JSON(200, utils.DynMap{"status": "logged out"}) + c.JSON(200, util.DynMap{"status": "logged out"}) } diff --git a/pkg/api/register.go b/pkg/api/register.go index 265a6038d73..410f0d304af 100644 --- a/pkg/api/register.go +++ b/pkg/api/register.go @@ -4,6 +4,7 @@ import ( "github.com/torkelo/grafana-pro/pkg/bus" "github.com/torkelo/grafana-pro/pkg/middleware" m "github.com/torkelo/grafana-pro/pkg/models" + "github.com/torkelo/grafana-pro/pkg/util" ) func CreateAccount(c *middleware.Context) { @@ -15,9 +16,10 @@ func CreateAccount(c *middleware.Context) { } cmd.Login = cmd.Email - err := bus.Dispatch(&cmd) + cmd.Salt = util.GetRandomString(10) + cmd.Password = util.EncodePassword(cmd.Password, cmd.Salt) - if err != nil { + if err := bus.Dispatch(&cmd); err != nil { c.JsonApiErr(500, "failed to create account", err) return } diff --git a/pkg/api/render.go b/pkg/api/render.go index 833e1067268..6f9692a3144 100644 --- a/pkg/api/render.go +++ b/pkg/api/render.go @@ -6,12 +6,12 @@ import ( "github.com/torkelo/grafana-pro/pkg/components/renderer" "github.com/torkelo/grafana-pro/pkg/middleware" - "github.com/torkelo/grafana-pro/pkg/utils" + "github.com/torkelo/grafana-pro/pkg/util" ) func RenderToPng(c *middleware.Context) { accountId := c.GetAccountId() - queryReader := utils.NewUrlQueryReader(c.Req.URL) + queryReader := util.NewUrlQueryReader(c.Req.URL) queryParams := "?render&accountId=" + strconv.FormatInt(accountId, 10) + "&" + c.Req.URL.RawQuery renderOpts := &renderer.RenderOpts{ diff --git a/pkg/api/search.go b/pkg/api/search.go new file mode 100644 index 00000000000..26cee95c526 --- /dev/null +++ b/pkg/api/search.go @@ -0,0 +1,45 @@ +package api + +import ( + "regexp" + "strings" + + "github.com/torkelo/grafana-pro/pkg/bus" + "github.com/torkelo/grafana-pro/pkg/middleware" + m "github.com/torkelo/grafana-pro/pkg/models" +) + +func Search(c *middleware.Context) { + queryText := c.Query("q") + result := m.SearchResult{ + Dashboards: []*m.DashboardSearchHit{}, + Tags: []*m.DashboardTagCloudItem{}, + } + + if strings.HasPrefix(queryText, "tags!:") { + query := m.GetDashboardTagsQuery{} + err := bus.Dispatch(&query) + if err != nil { + c.JsonApiErr(500, "Failed to get tags from database", err) + return + } + result.Tags = query.Result + result.TagsOnly = true + } else { + searchQueryRegEx, _ := regexp.Compile(`(tags:(\w*)\sAND\s)?(?:title:)?(.*)?`) + matches := searchQueryRegEx.FindStringSubmatch(queryText) + query := m.SearchDashboardsQuery{ + Title: matches[3], + Tag: matches[2], + AccountId: c.GetAccountId(), + } + err := bus.Dispatch(&query) + if err != nil { + c.JsonApiErr(500, "Search failed", err) + return + } + result.Dashboards = query.Result + } + + c.JSON(200, result) +} diff --git a/pkg/models/account.go b/pkg/models/account.go index c7ba4d4e8c7..9c95efe8739 100644 --- a/pkg/models/account.go +++ b/pkg/models/account.go @@ -18,6 +18,7 @@ type Account struct { FullName string Password string IsAdmin bool + Rands string `xorm:"VARCHAR(10)"` Salt string `xorm:"VARCHAR(10)"` Company string NextDashboardId int @@ -55,6 +56,7 @@ type CreateAccountCommand struct { Password string `json:"password" binding:"required"` Name string `json:"name"` Company string `json:"company"` + Salt string `json:"-"` Result Account `json:"-"` } diff --git a/pkg/stores/sqlstore/accounts.go b/pkg/stores/sqlstore/accounts.go index 51790e27ee0..4bfa3d60c3a 100644 --- a/pkg/stores/sqlstore/accounts.go +++ b/pkg/stores/sqlstore/accounts.go @@ -26,6 +26,7 @@ func CreateAccount(cmd *m.CreateAccountCommand) error { Email: cmd.Email, Login: cmd.Login, Password: cmd.Password, + Salt: cmd.Salt, Created: time.Now(), Updated: time.Now(), } diff --git a/pkg/util/encoding.go b/pkg/util/encoding.go new file mode 100644 index 00000000000..e69d688492b --- /dev/null +++ b/pkg/util/encoding.go @@ -0,0 +1,67 @@ +package util + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "fmt" + "hash" +) + +// source: https://github.com/gogits/gogs/blob/9ee80e3e5426821f03a4e99fad34418f5c736413/modules/base/tool.go#L58 +func GetRandomString(n int, alphabets ...byte) string { + const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + var bytes = make([]byte, n) + rand.Read(bytes) + for i, b := range bytes { + if len(alphabets) == 0 { + bytes[i] = alphanum[b%byte(len(alphanum))] + } else { + bytes[i] = alphabets[b%byte(len(alphabets))] + } + } + return string(bytes) +} + +func EncodePassword(password string, salt string) string { + newPasswd := PBKDF2([]byte(password), []byte(salt), 10000, 50, sha256.New) + return fmt.Sprintf("%x", newPasswd) +} + +// http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto +func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte { + prf := hmac.New(h, password) + hashLen := prf.Size() + numBlocks := (keyLen + hashLen - 1) / hashLen + + var buf [4]byte + dk := make([]byte, 0, numBlocks*hashLen) + U := make([]byte, hashLen) + for block := 1; block <= numBlocks; block++ { + // N.B.: || means concatenation, ^ means XOR + // for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter + // U_1 = PRF(password, salt || uint(i)) + prf.Reset() + prf.Write(salt) + buf[0] = byte(block >> 24) + buf[1] = byte(block >> 16) + buf[2] = byte(block >> 8) + buf[3] = byte(block) + prf.Write(buf[:4]) + dk = prf.Sum(dk) + T := dk[len(dk)-hashLen:] + copy(U, T) + + // U_n = PRF(password, U_(n-1)) + for n := 2; n <= iter; n++ { + prf.Reset() + prf.Write(U) + U = U[:0] + U = prf.Sum(U) + for x := range U { + T[x] ^= U[x] + } + } + } + return dk[:keyLen] +} diff --git a/pkg/utils/json.go b/pkg/util/json.go similarity index 72% rename from pkg/utils/json.go rename to pkg/util/json.go index 0685209c8e0..a75a8490d93 100644 --- a/pkg/utils/json.go +++ b/pkg/util/json.go @@ -1,3 +1,3 @@ -package utils +package util type DynMap map[string]interface{} diff --git a/pkg/utils/url.go b/pkg/util/url.go similarity index 97% rename from pkg/utils/url.go rename to pkg/util/url.go index 42991427df8..758e6864fe4 100644 --- a/pkg/utils/url.go +++ b/pkg/util/url.go @@ -1,4 +1,4 @@ -package utils +package util import ( "net/url"