Browse Source

Fixed hashing of passwords, Closes #3

Torkel Ödegaard 11 years ago
parent
commit
3226a3a58e

+ 1 - 1
grafana

@@ -1 +1 @@
-Subproject commit d3e11cabd51d082244f83258b02fd635cc780c08
+Subproject commit 164435f71d3462fa1719791e1c8eb9841374d299

+ 2 - 2
pkg/api/account.go

@@ -4,7 +4,7 @@ import (
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	m "github.com/torkelo/grafana-pro/pkg/models"
 	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) {
 func GetAccount(c *middleware.Context) {
@@ -59,7 +59,7 @@ func GetOtherAccounts(c *middleware.Context) {
 	err := bus.Dispatch(&query)
 	err := bus.Dispatch(&query)
 
 
 	if err != nil {
 	if err != nil {
-		c.JSON(500, utils.DynMap{"message": err.Error()})
+		c.JSON(500, util.DynMap{"message": err.Error()})
 		return
 		return
 	}
 	}
 
 

+ 4 - 44
pkg/api/dashboard.go

@@ -1,13 +1,10 @@
 package api
 package api
 
 
 import (
 import (
-	"regexp"
-	"strings"
-
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	m "github.com/torkelo/grafana-pro/pkg/models"
 	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) {
 func GetDashboard(c *middleware.Context) {
@@ -29,15 +26,13 @@ func DeleteDashboard(c *middleware.Context) {
 	slug := c.Params(":slug")
 	slug := c.Params(":slug")
 
 
 	query := m.GetDashboardQuery{Slug: slug, AccountId: c.GetAccountId()}
 	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)
 		c.JsonApiErr(404, "Dashboard not found", nil)
 		return
 		return
 	}
 	}
 
 
 	cmd := m.DeleteDashboardCommand{Slug: slug, AccountId: c.GetAccountId()}
 	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)
 		c.JsonApiErr(500, "Failed to delete dashboard", err)
 		return
 		return
 	}
 	}
@@ -47,41 +42,6 @@ func DeleteDashboard(c *middleware.Context) {
 	c.JSON(200, resp)
 	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) {
 func PostDashboard(c *middleware.Context) {
 	var cmd m.SaveDashboardCommand
 	var cmd m.SaveDashboardCommand
 
 
@@ -102,5 +62,5 @@ func PostDashboard(c *middleware.Context) {
 		return
 		return
 	}
 	}
 
 
-	c.JSON(200, utils.DynMap{"status": "success", "slug": cmd.Result.Slug})
+	c.JSON(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug})
 }
 }

+ 3 - 3
pkg/api/dataproxy.go

@@ -8,7 +8,7 @@ import (
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	m "github.com/torkelo/grafana-pro/pkg/models"
 	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 {
 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()
 		reqQueryVals := req.URL.Query()
 
 
 		if ds.Type == m.DS_INFLUXDB {
 		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("u", ds.User)
 			reqQueryVals.Add("p", ds.Password)
 			reqQueryVals.Add("p", ds.Password)
 			req.URL.RawQuery = reqQueryVals.Encode()
 			req.URL.RawQuery = reqQueryVals.Encode()
 		} else {
 		} else {
-			req.URL.Path = utils.JoinUrlFragments(target.Path, proxyPath)
+			req.URL.Path = util.JoinUrlFragments(target.Path, proxyPath)
 		}
 		}
 	}
 	}
 
 

+ 5 - 4
pkg/api/login.go

@@ -6,7 +6,7 @@ import (
 	"github.com/torkelo/grafana-pro/pkg/log"
 	"github.com/torkelo/grafana-pro/pkg/log"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	m "github.com/torkelo/grafana-pro/pkg/models"
 	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 {
 type loginJsonModel struct {
@@ -19,7 +19,7 @@ func LoginPost(c *middleware.Context) {
 	var loginModel loginJsonModel
 	var loginModel loginJsonModel
 
 
 	if !c.JsonBody(&loginModel) {
 	if !c.JsonBody(&loginModel) {
-		c.JSON(400, utils.DynMap{"status": "bad request"})
+		c.JSON(400, util.DynMap{"message": "bad request"})
 		return
 		return
 	}
 	}
 
 
@@ -33,7 +33,8 @@ func LoginPost(c *middleware.Context) {
 
 
 	account := userQuery.Result
 	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)
 		c.JsonApiErr(401, "Invalid username or password", err)
 		return
 		return
 	}
 	}
@@ -57,5 +58,5 @@ func loginUserWithAccount(account *m.Account, c *middleware.Context) {
 
 
 func LogoutPost(c *middleware.Context) {
 func LogoutPost(c *middleware.Context) {
 	c.Session.Delete("accountId")
 	c.Session.Delete("accountId")
-	c.JSON(200, utils.DynMap{"status": "logged out"})
+	c.JSON(200, util.DynMap{"status": "logged out"})
 }
 }

+ 4 - 2
pkg/api/register.go

@@ -4,6 +4,7 @@ import (
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/bus"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	m "github.com/torkelo/grafana-pro/pkg/models"
 	m "github.com/torkelo/grafana-pro/pkg/models"
+	"github.com/torkelo/grafana-pro/pkg/util"
 )
 )
 
 
 func CreateAccount(c *middleware.Context) {
 func CreateAccount(c *middleware.Context) {
@@ -15,9 +16,10 @@ func CreateAccount(c *middleware.Context) {
 	}
 	}
 
 
 	cmd.Login = cmd.Email
 	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)
 		c.JsonApiErr(500, "failed to create account", err)
 		return
 		return
 	}
 	}

+ 2 - 2
pkg/api/render.go

@@ -6,12 +6,12 @@ import (
 
 
 	"github.com/torkelo/grafana-pro/pkg/components/renderer"
 	"github.com/torkelo/grafana-pro/pkg/components/renderer"
 	"github.com/torkelo/grafana-pro/pkg/middleware"
 	"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) {
 func RenderToPng(c *middleware.Context) {
 	accountId := c.GetAccountId()
 	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
 	queryParams := "?render&accountId=" + strconv.FormatInt(accountId, 10) + "&" + c.Req.URL.RawQuery
 
 
 	renderOpts := &renderer.RenderOpts{
 	renderOpts := &renderer.RenderOpts{

+ 45 - 0
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)
+}

+ 2 - 0
pkg/models/account.go

@@ -18,6 +18,7 @@ type Account struct {
 	FullName        string
 	FullName        string
 	Password        string
 	Password        string
 	IsAdmin         bool
 	IsAdmin         bool
+	Rands           string `xorm:"VARCHAR(10)"`
 	Salt            string `xorm:"VARCHAR(10)"`
 	Salt            string `xorm:"VARCHAR(10)"`
 	Company         string
 	Company         string
 	NextDashboardId int
 	NextDashboardId int
@@ -55,6 +56,7 @@ type CreateAccountCommand struct {
 	Password string `json:"password" binding:"required"`
 	Password string `json:"password" binding:"required"`
 	Name     string `json:"name"`
 	Name     string `json:"name"`
 	Company  string `json:"company"`
 	Company  string `json:"company"`
+	Salt     string `json:"-"`
 
 
 	Result Account `json:"-"`
 	Result Account `json:"-"`
 }
 }

+ 1 - 0
pkg/stores/sqlstore/accounts.go

@@ -26,6 +26,7 @@ func CreateAccount(cmd *m.CreateAccountCommand) error {
 			Email:    cmd.Email,
 			Email:    cmd.Email,
 			Login:    cmd.Login,
 			Login:    cmd.Login,
 			Password: cmd.Password,
 			Password: cmd.Password,
+			Salt:     cmd.Salt,
 			Created:  time.Now(),
 			Created:  time.Now(),
 			Updated:  time.Now(),
 			Updated:  time.Now(),
 		}
 		}

+ 67 - 0
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]
+}

+ 1 - 1
pkg/utils/json.go → pkg/util/json.go

@@ -1,3 +1,3 @@
-package utils
+package util
 
 
 type DynMap map[string]interface{}
 type DynMap map[string]interface{}

+ 1 - 1
pkg/utils/url.go → pkg/util/url.go

@@ -1,4 +1,4 @@
-package utils
+package util
 
 
 import (
 import (
 	"net/url"
 	"net/url"