diff --git a/002-manpages.patch b/002-manpages.patch index 36ca294..d982b5d 100644 --- a/002-manpages.patch +++ b/002-manpages.patch @@ -4,7 +4,7 @@ index 0000000000..7ac2af882c --- /dev/null +++ b/docs/man/man1/grafana-cli.1 @@ -0,0 +1,60 @@ -+.TH GRAFANA "1" "October 2021" "Grafana cli version 7.5.11" "User Commands" ++.TH GRAFANA "1" "January 2022" "Grafana cli version 7.5.13" "User Commands" +.SH NAME +grafana-cli \- command line administration for the Grafana metrics dashboard and graph editor +.SH DESCRIPTION @@ -70,7 +70,7 @@ index 0000000000..c616268b31 --- /dev/null +++ b/docs/man/man1/grafana-server.1 @@ -0,0 +1,72 @@ -+.TH VERSION "1" "October 2021" "Version 7.5.11" "User Commands" ++.TH VERSION "1" "January 2022" "Version 7.5.13" "User Commands" +.SH NAME +grafana-server \- back-end server for the Grafana metrics dashboard and graph editor +.SH DESCRIPTION diff --git a/011-CVE-2021-43813.patch b/011-CVE-2021-43813.patch deleted file mode 100644 index 375b364..0000000 --- a/011-CVE-2021-43813.patch +++ /dev/null @@ -1,52 +0,0 @@ -commit ea77415cfe2cefe46ffce233076a1409abaa8df7 -Author: Will Browne -Date: Fri Dec 10 11:29:12 2021 +0000 - - apply fix (#42969) - -diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go -index e6370a29e7..c7199c716e 100644 ---- a/pkg/plugins/plugins.go -+++ b/pkg/plugins/plugins.go -@@ -491,15 +491,15 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { - } - - // nolint:gosec -- // We can ignore the gosec G304 warning on this one because `plug.PluginDir` is based -- // on plugin the folder structure on disk and not user input. -- path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name))) -+ // We can ignore the gosec G304 warning since we have cleaned the requested file path and subsequently -+ // use this with a prefix of the plugin's directory, which is set during plugin loading -+ path := filepath.Join(plug.PluginDir, mdFilepath(strings.ToUpper(name))) - exists, err := fs.Exists(path) - if err != nil { - return nil, err - } - if !exists { -- path = filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name))) -+ path = filepath.Join(plug.PluginDir, mdFilepath(strings.ToLower(name))) - } - - exists, err = fs.Exists(path) -@@ -511,8 +511,8 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { - } - - // nolint:gosec -- // We can ignore the gosec G304 warning on this one because `plug.PluginDir` is based -- // on plugin the folder structure on disk and not user input. -+ // We can ignore the gosec G304 warning since we have cleaned the requested file path and subsequently -+ // use this with a prefix of the plugin's directory, which is set during plugin loading - data, err := ioutil.ReadFile(path) - if err != nil { - return nil, err -@@ -520,6 +520,10 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { - return data, nil - } - -+func mdFilepath(mdFilename string) string { -+ return filepath.Clean(filepath.Join("/", fmt.Sprintf("%s.md", mdFilename))) -+} -+ - // gets plugin filenames that require verification for plugin signing - func collectPluginFilesWithin(rootDir string) ([]string, error) { - var files []string diff --git a/011-use-hmac-sha-256-for-password-reset-tokens.patch b/011-use-hmac-sha-256-for-password-reset-tokens.patch new file mode 100644 index 0000000..91b6b46 --- /dev/null +++ b/011-use-hmac-sha-256-for-password-reset-tokens.patch @@ -0,0 +1,353 @@ +commit f13c08e9f45d7776cb264b17ec41bc4ff51fc0b9 +Author: Andreas Gerstmayr +Date: Thu Nov 25 18:49:52 2021 +0100 + + notifications: use HMAC-SHA256 to generate time limit codes + + * changes the time limit code generation function to use HMAC-SHA256 + instead of SHA-1 + * multiple new testcases + +diff --git a/pkg/services/notifications/codes.go b/pkg/services/notifications/codes.go +index ea9beb30cc..1ddf05dc69 100644 +--- a/pkg/services/notifications/codes.go ++++ b/pkg/services/notifications/codes.go +@@ -1,48 +1,53 @@ + package notifications + + import ( +- "crypto/sha1" // #nosec ++ "crypto/hmac" ++ "crypto/sha256" + "encoding/hex" + "fmt" ++ "strconv" + "time" + +- "github.com/unknwon/com" +- + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + ) + +-const timeLimitCodeLength = 12 + 6 + 40 ++const timeLimitStartDateLength = 12 ++const timeLimitMinutesLength = 6 ++const timeLimitHmacLength = 64 ++const timeLimitCodeLength = timeLimitStartDateLength + timeLimitMinutesLength + timeLimitHmacLength + + // create a time limit code +-// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string +-func createTimeLimitCode(data string, minutes int, startInf interface{}) (string, error) { ++// code format: 12 length date time string + 6 minutes string + 64 HMAC-SHA256 encoded string ++func createTimeLimitCode(payload string, minutes int, startStr string) (string, error) { + format := "200601021504" + + var start, end time.Time +- var startStr, endStr string ++ var endStr string + +- if startInf == nil { ++ if startStr == "" { + // Use now time create code + start = time.Now() + startStr = start.Format(format) + } else { + // use start string create code +- startStr = startInf.(string) +- start, _ = time.ParseInLocation(format, startStr, time.Local) +- startStr = start.Format(format) ++ var err error ++ start, err = time.ParseInLocation(format, startStr, time.Local) ++ if err != nil { ++ return "", err ++ } + } + + end = start.Add(time.Minute * time.Duration(minutes)) + endStr = end.Format(format) + +- // create sha1 encode string +- sh := sha1.New() +- if _, err := sh.Write([]byte(data + setting.SecretKey + startStr + endStr + +- com.ToStr(minutes))); err != nil { +- return "", err ++ // create HMAC-SHA256 encoded string ++ key := []byte(setting.SecretKey) ++ h := hmac.New(sha256.New, key) ++ if _, err := h.Write([]byte(payload + startStr + endStr)); err != nil { ++ return "", fmt.Errorf("cannot create hmac: %v", err) + } +- encoded := hex.EncodeToString(sh.Sum(nil)) ++ encoded := hex.EncodeToString(h.Sum(nil)) + + code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) + return code, nil +@@ -50,30 +55,29 @@ func createTimeLimitCode(data string, minutes int, startInf interface{}) (string + + // verify time limit code + func validateUserEmailCode(user *models.User, code string) (bool, error) { +- if len(code) <= 18 { ++ if len(code) < timeLimitCodeLength { + return false, nil + } + +- minutes := setting.EmailCodeValidMinutes + code = code[:timeLimitCodeLength] + + // split code +- start := code[:12] +- lives := code[12:18] +- if d, err := com.StrTo(lives).Int(); err == nil { +- minutes = d ++ startStr := code[:timeLimitStartDateLength] ++ minutesStr := code[timeLimitStartDateLength : timeLimitStartDateLength+timeLimitMinutesLength] ++ minutes, err := strconv.Atoi(minutesStr) ++ if err != nil { ++ return false, fmt.Errorf("invalid time limit code: %v", err) + } + +- // right active code +- data := com.ToStr(user.Id) + user.Email + user.Login + user.Password + user.Rands +- retCode, err := createTimeLimitCode(data, minutes, start) ++ // verify code ++ payload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands ++ expectedCode, err := createTimeLimitCode(payload, minutes, startStr) + if err != nil { + return false, err + } +- fmt.Printf("code : %s\ncode2: %s", retCode, code) +- if retCode == code && minutes > 0 { ++ if hmac.Equal([]byte(code), []byte(expectedCode)) && minutes > 0 { + // check time is expired or not +- before, _ := time.ParseInLocation("200601021504", start, time.Local) ++ before, _ := time.ParseInLocation("200601021504", startStr, time.Local) + now := time.Now() + if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() { + return true, nil +@@ -94,15 +98,15 @@ func getLoginForEmailCode(code string) string { + return string(b) + } + +-func createUserEmailCode(u *models.User, startInf interface{}) (string, error) { ++func createUserEmailCode(user *models.User, startStr string) (string, error) { + minutes := setting.EmailCodeValidMinutes +- data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands +- code, err := createTimeLimitCode(data, minutes, startInf) ++ payload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands ++ code, err := createTimeLimitCode(payload, minutes, startStr) + if err != nil { + return "", err + } + + // add tail hex username +- code += hex.EncodeToString([]byte(u.Login)) ++ code += hex.EncodeToString([]byte(user.Login)) + return code, nil + } +diff --git a/pkg/services/notifications/codes_test.go b/pkg/services/notifications/codes_test.go +index d2b1f3a617..bea88e0bf5 100644 +--- a/pkg/services/notifications/codes_test.go ++++ b/pkg/services/notifications/codes_test.go +@@ -1,19 +1,129 @@ + package notifications + + import ( ++ "fmt" ++ "strconv" + "testing" ++ "time" + + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" ++ "github.com/stretchr/testify/require" + ) + ++func TestTimeLimitCodes(t *testing.T) { ++ user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} ++ ++ format := "200601021504" ++ mailPayload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands ++ tenMinutesAgo := time.Now().Add(-time.Minute * 10) ++ ++ tests := []struct { ++ desc string ++ payload string ++ start time.Time ++ minutes int ++ valid bool ++ }{ ++ { ++ desc: "code generated 10 minutes ago, 5 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 5, ++ valid: false, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 9 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 9, ++ valid: false, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 10 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 10, ++ // code was valid exactly 10 minutes since evaluating the tenMinutesAgo assignment ++ // by the time this test is run the code is already expired ++ valid: false, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 11 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 11, ++ valid: true, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 20 minutes valid", ++ payload: mailPayload, ++ start: tenMinutesAgo, ++ minutes: 20, ++ valid: true, ++ }, ++ { ++ desc: "code generated 10 minutes ago, 20 minutes valid, tampered payload", ++ payload: mailPayload[:len(mailPayload)-1] + "x", ++ start: tenMinutesAgo, ++ minutes: 20, ++ valid: false, ++ }, ++ } ++ ++ for _, test := range tests { ++ t.Run(test.desc, func(t *testing.T) { ++ code, err := createTimeLimitCode(test.payload, test.minutes, test.start.Format(format)) ++ require.NoError(t, err) ++ ++ isValid, err := validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, test.valid, isValid) ++ }) ++ } ++ ++ t.Run("tampered minutes", func(t *testing.T) { ++ code, err := createTimeLimitCode(mailPayload, 5, tenMinutesAgo.Format(format)) ++ require.NoError(t, err) ++ ++ // code is expired ++ isValid, err := validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ ++ // let's try to extend the code by tampering the minutes ++ code = code[:12] + fmt.Sprintf("%06d", 20) + code[18:] ++ isValid, err = validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ }) ++ ++ t.Run("tampered start string", func(t *testing.T) { ++ code, err := createTimeLimitCode(mailPayload, 5, tenMinutesAgo.Format(format)) ++ require.NoError(t, err) ++ ++ // code is expired ++ isValid, err := validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ ++ // let's try to extend the code by tampering the start string ++ oneMinuteAgo := time.Now().Add(-time.Minute) ++ ++ code = oneMinuteAgo.Format(format) + code[12:] ++ isValid, err = validateUserEmailCode(user, code) ++ require.NoError(t, err) ++ require.Equal(t, false, isValid) ++ }) ++} ++ + func TestEmailCodes(t *testing.T) { + Convey("When generating code", t, func() { + setting.EmailCodeValidMinutes = 120 + + user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} +- code, err := createUserEmailCode(user, nil) ++ code, err := createUserEmailCode(user, "") + So(err, ShouldBeNil) + + Convey("getLoginForCode should return login", func() { +@@ -27,7 +137,7 @@ func TestEmailCodes(t *testing.T) { + So(isValid, ShouldBeTrue) + }) + +- Convey("Cannot verify in-valid code", func() { ++ Convey("Cannot verify invalid code", func() { + code = "ASD" + isValid, err := validateUserEmailCode(user, code) + So(err, ShouldBeNil) +diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go +index beea82f43e..5a575d1415 100644 +--- a/pkg/services/notifications/notifications.go ++++ b/pkg/services/notifications/notifications.go +@@ -149,7 +149,7 @@ func (ns *NotificationService) sendEmailCommandHandler(cmd *models.SendEmailComm + } + + func (ns *NotificationService) sendResetPasswordEmail(cmd *models.SendResetPasswordEmailCommand) error { +- code, err := createUserEmailCode(cmd.User, nil) ++ code, err := createUserEmailCode(cmd.User, "") + if err != nil { + return err + } +diff --git a/pkg/services/notifications/notifications_test.go b/pkg/services/notifications/notifications_test.go +index e7680c3943..fb73e332ea 100644 +--- a/pkg/services/notifications/notifications_test.go ++++ b/pkg/services/notifications/notifications_test.go +@@ -1,12 +1,14 @@ + package notifications + + import ( ++ "regexp" + "testing" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" ++ "github.com/stretchr/testify/require" + ) + + func TestNotifications(t *testing.T) { +@@ -25,13 +27,28 @@ func TestNotifications(t *testing.T) { + So(err, ShouldBeNil) + + Convey("When sending reset email password", func() { +- err := ns.sendResetPasswordEmail(&models.SendResetPasswordEmailCommand{User: &models.User{Email: "asd@asd.com"}}) ++ user := models.User{Email: "asd@asd.com", Login: "asd@asd.com"} ++ err := ns.sendResetPasswordEmail(&models.SendResetPasswordEmailCommand{User: &user}) + So(err, ShouldBeNil) + + sentMsg := <-ns.mailQueue + So(sentMsg.Body, ShouldContainSubstring, "body") + So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password - asd@asd.com") + So(sentMsg.Body, ShouldNotContainSubstring, "Subject") ++ ++ // find code in mail ++ r, _ := regexp.Compile(`code=(\w+)`) ++ match := r.FindString(sentMsg.Body) ++ code := match[len("code="):] ++ ++ // verify code ++ bus.AddHandler("test", func(query *models.GetUserByLoginQuery) error { ++ query.Result = &user ++ return nil ++ }) ++ query := models.ValidateResetPasswordCodeQuery{Code: code} ++ err = ns.validateResetPasswordCode(&query) ++ require.NoError(t, err) + }) + }) + } diff --git a/012-support-go1.18.patch b/012-support-go1.18.patch new file mode 100644 index 0000000..4e6d70e --- /dev/null +++ b/012-support-go1.18.patch @@ -0,0 +1,28 @@ +# json-iterator/go supports go1.18 since v1.1.12 +# https://github.com/json-iterator/go/releases/tag/v1.1.12 +# +# gonum.org/v1/gonum supports go1.18 since commit cccd8af5f6bd1539dd688c88102cb37e9117f96a +# https://github.com/gonum/gonum/pull/1729 + +diff --git a/go.mod b/go.mod +index 67932fe072..2aa638fe9a 100644 +--- a/go.mod ++++ b/go.mod +@@ -55,7 +55,7 @@ require ( + github.com/jaegertracing/jaeger v1.22.1-0.20210304164023-2fff3ca58910 + github.com/jmespath/go-jmespath v0.4.0 + github.com/jonboulle/clockwork v0.2.2 // indirect +- github.com/json-iterator/go v1.1.10 ++ github.com/json-iterator/go v1.1.12 + github.com/lib/pq v1.9.0 + github.com/linkedin/goavro/v2 v2.10.0 + github.com/magefile/mage v1.11.0 +@@ -88,7 +88,7 @@ require ( + golang.org/x/oauth2 v0.0.0-20210113205817-d3ed898aa8a3 + golang.org/x/sync v0.0.0-20201207232520-09787c993a3a + golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e +- gonum.org/v1/gonum v0.8.2 ++ gonum.org/v1/gonum v0.9.1-0.20220120213227-d4eca1bbc084 + google.golang.org/api v0.40.0 + google.golang.org/grpc v1.36.0 + gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect diff --git a/012-use-hmac-sha-256-for-password-reset-tokens.patch b/012-use-hmac-sha-256-for-password-reset-tokens.patch deleted file mode 100644 index 91b6b46..0000000 --- a/012-use-hmac-sha-256-for-password-reset-tokens.patch +++ /dev/null @@ -1,353 +0,0 @@ -commit f13c08e9f45d7776cb264b17ec41bc4ff51fc0b9 -Author: Andreas Gerstmayr -Date: Thu Nov 25 18:49:52 2021 +0100 - - notifications: use HMAC-SHA256 to generate time limit codes - - * changes the time limit code generation function to use HMAC-SHA256 - instead of SHA-1 - * multiple new testcases - -diff --git a/pkg/services/notifications/codes.go b/pkg/services/notifications/codes.go -index ea9beb30cc..1ddf05dc69 100644 ---- a/pkg/services/notifications/codes.go -+++ b/pkg/services/notifications/codes.go -@@ -1,48 +1,53 @@ - package notifications - - import ( -- "crypto/sha1" // #nosec -+ "crypto/hmac" -+ "crypto/sha256" - "encoding/hex" - "fmt" -+ "strconv" - "time" - -- "github.com/unknwon/com" -- - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" - ) - --const timeLimitCodeLength = 12 + 6 + 40 -+const timeLimitStartDateLength = 12 -+const timeLimitMinutesLength = 6 -+const timeLimitHmacLength = 64 -+const timeLimitCodeLength = timeLimitStartDateLength + timeLimitMinutesLength + timeLimitHmacLength - - // create a time limit code --// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string --func createTimeLimitCode(data string, minutes int, startInf interface{}) (string, error) { -+// code format: 12 length date time string + 6 minutes string + 64 HMAC-SHA256 encoded string -+func createTimeLimitCode(payload string, minutes int, startStr string) (string, error) { - format := "200601021504" - - var start, end time.Time -- var startStr, endStr string -+ var endStr string - -- if startInf == nil { -+ if startStr == "" { - // Use now time create code - start = time.Now() - startStr = start.Format(format) - } else { - // use start string create code -- startStr = startInf.(string) -- start, _ = time.ParseInLocation(format, startStr, time.Local) -- startStr = start.Format(format) -+ var err error -+ start, err = time.ParseInLocation(format, startStr, time.Local) -+ if err != nil { -+ return "", err -+ } - } - - end = start.Add(time.Minute * time.Duration(minutes)) - endStr = end.Format(format) - -- // create sha1 encode string -- sh := sha1.New() -- if _, err := sh.Write([]byte(data + setting.SecretKey + startStr + endStr + -- com.ToStr(minutes))); err != nil { -- return "", err -+ // create HMAC-SHA256 encoded string -+ key := []byte(setting.SecretKey) -+ h := hmac.New(sha256.New, key) -+ if _, err := h.Write([]byte(payload + startStr + endStr)); err != nil { -+ return "", fmt.Errorf("cannot create hmac: %v", err) - } -- encoded := hex.EncodeToString(sh.Sum(nil)) -+ encoded := hex.EncodeToString(h.Sum(nil)) - - code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded) - return code, nil -@@ -50,30 +55,29 @@ func createTimeLimitCode(data string, minutes int, startInf interface{}) (string - - // verify time limit code - func validateUserEmailCode(user *models.User, code string) (bool, error) { -- if len(code) <= 18 { -+ if len(code) < timeLimitCodeLength { - return false, nil - } - -- minutes := setting.EmailCodeValidMinutes - code = code[:timeLimitCodeLength] - - // split code -- start := code[:12] -- lives := code[12:18] -- if d, err := com.StrTo(lives).Int(); err == nil { -- minutes = d -+ startStr := code[:timeLimitStartDateLength] -+ minutesStr := code[timeLimitStartDateLength : timeLimitStartDateLength+timeLimitMinutesLength] -+ minutes, err := strconv.Atoi(minutesStr) -+ if err != nil { -+ return false, fmt.Errorf("invalid time limit code: %v", err) - } - -- // right active code -- data := com.ToStr(user.Id) + user.Email + user.Login + user.Password + user.Rands -- retCode, err := createTimeLimitCode(data, minutes, start) -+ // verify code -+ payload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands -+ expectedCode, err := createTimeLimitCode(payload, minutes, startStr) - if err != nil { - return false, err - } -- fmt.Printf("code : %s\ncode2: %s", retCode, code) -- if retCode == code && minutes > 0 { -+ if hmac.Equal([]byte(code), []byte(expectedCode)) && minutes > 0 { - // check time is expired or not -- before, _ := time.ParseInLocation("200601021504", start, time.Local) -+ before, _ := time.ParseInLocation("200601021504", startStr, time.Local) - now := time.Now() - if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() { - return true, nil -@@ -94,15 +98,15 @@ func getLoginForEmailCode(code string) string { - return string(b) - } - --func createUserEmailCode(u *models.User, startInf interface{}) (string, error) { -+func createUserEmailCode(user *models.User, startStr string) (string, error) { - minutes := setting.EmailCodeValidMinutes -- data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands -- code, err := createTimeLimitCode(data, minutes, startInf) -+ payload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands -+ code, err := createTimeLimitCode(payload, minutes, startStr) - if err != nil { - return "", err - } - - // add tail hex username -- code += hex.EncodeToString([]byte(u.Login)) -+ code += hex.EncodeToString([]byte(user.Login)) - return code, nil - } -diff --git a/pkg/services/notifications/codes_test.go b/pkg/services/notifications/codes_test.go -index d2b1f3a617..bea88e0bf5 100644 ---- a/pkg/services/notifications/codes_test.go -+++ b/pkg/services/notifications/codes_test.go -@@ -1,19 +1,129 @@ - package notifications - - import ( -+ "fmt" -+ "strconv" - "testing" -+ "time" - - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" - . "github.com/smartystreets/goconvey/convey" -+ "github.com/stretchr/testify/require" - ) - -+func TestTimeLimitCodes(t *testing.T) { -+ user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} -+ -+ format := "200601021504" -+ mailPayload := strconv.FormatInt(user.Id, 10) + user.Email + user.Login + user.Password + user.Rands -+ tenMinutesAgo := time.Now().Add(-time.Minute * 10) -+ -+ tests := []struct { -+ desc string -+ payload string -+ start time.Time -+ minutes int -+ valid bool -+ }{ -+ { -+ desc: "code generated 10 minutes ago, 5 minutes valid", -+ payload: mailPayload, -+ start: tenMinutesAgo, -+ minutes: 5, -+ valid: false, -+ }, -+ { -+ desc: "code generated 10 minutes ago, 9 minutes valid", -+ payload: mailPayload, -+ start: tenMinutesAgo, -+ minutes: 9, -+ valid: false, -+ }, -+ { -+ desc: "code generated 10 minutes ago, 10 minutes valid", -+ payload: mailPayload, -+ start: tenMinutesAgo, -+ minutes: 10, -+ // code was valid exactly 10 minutes since evaluating the tenMinutesAgo assignment -+ // by the time this test is run the code is already expired -+ valid: false, -+ }, -+ { -+ desc: "code generated 10 minutes ago, 11 minutes valid", -+ payload: mailPayload, -+ start: tenMinutesAgo, -+ minutes: 11, -+ valid: true, -+ }, -+ { -+ desc: "code generated 10 minutes ago, 20 minutes valid", -+ payload: mailPayload, -+ start: tenMinutesAgo, -+ minutes: 20, -+ valid: true, -+ }, -+ { -+ desc: "code generated 10 minutes ago, 20 minutes valid, tampered payload", -+ payload: mailPayload[:len(mailPayload)-1] + "x", -+ start: tenMinutesAgo, -+ minutes: 20, -+ valid: false, -+ }, -+ } -+ -+ for _, test := range tests { -+ t.Run(test.desc, func(t *testing.T) { -+ code, err := createTimeLimitCode(test.payload, test.minutes, test.start.Format(format)) -+ require.NoError(t, err) -+ -+ isValid, err := validateUserEmailCode(user, code) -+ require.NoError(t, err) -+ require.Equal(t, test.valid, isValid) -+ }) -+ } -+ -+ t.Run("tampered minutes", func(t *testing.T) { -+ code, err := createTimeLimitCode(mailPayload, 5, tenMinutesAgo.Format(format)) -+ require.NoError(t, err) -+ -+ // code is expired -+ isValid, err := validateUserEmailCode(user, code) -+ require.NoError(t, err) -+ require.Equal(t, false, isValid) -+ -+ // let's try to extend the code by tampering the minutes -+ code = code[:12] + fmt.Sprintf("%06d", 20) + code[18:] -+ isValid, err = validateUserEmailCode(user, code) -+ require.NoError(t, err) -+ require.Equal(t, false, isValid) -+ }) -+ -+ t.Run("tampered start string", func(t *testing.T) { -+ code, err := createTimeLimitCode(mailPayload, 5, tenMinutesAgo.Format(format)) -+ require.NoError(t, err) -+ -+ // code is expired -+ isValid, err := validateUserEmailCode(user, code) -+ require.NoError(t, err) -+ require.Equal(t, false, isValid) -+ -+ // let's try to extend the code by tampering the start string -+ oneMinuteAgo := time.Now().Add(-time.Minute) -+ -+ code = oneMinuteAgo.Format(format) + code[12:] -+ isValid, err = validateUserEmailCode(user, code) -+ require.NoError(t, err) -+ require.Equal(t, false, isValid) -+ }) -+} -+ - func TestEmailCodes(t *testing.T) { - Convey("When generating code", t, func() { - setting.EmailCodeValidMinutes = 120 - - user := &models.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"} -- code, err := createUserEmailCode(user, nil) -+ code, err := createUserEmailCode(user, "") - So(err, ShouldBeNil) - - Convey("getLoginForCode should return login", func() { -@@ -27,7 +137,7 @@ func TestEmailCodes(t *testing.T) { - So(isValid, ShouldBeTrue) - }) - -- Convey("Cannot verify in-valid code", func() { -+ Convey("Cannot verify invalid code", func() { - code = "ASD" - isValid, err := validateUserEmailCode(user, code) - So(err, ShouldBeNil) -diff --git a/pkg/services/notifications/notifications.go b/pkg/services/notifications/notifications.go -index beea82f43e..5a575d1415 100644 ---- a/pkg/services/notifications/notifications.go -+++ b/pkg/services/notifications/notifications.go -@@ -149,7 +149,7 @@ func (ns *NotificationService) sendEmailCommandHandler(cmd *models.SendEmailComm - } - - func (ns *NotificationService) sendResetPasswordEmail(cmd *models.SendResetPasswordEmailCommand) error { -- code, err := createUserEmailCode(cmd.User, nil) -+ code, err := createUserEmailCode(cmd.User, "") - if err != nil { - return err - } -diff --git a/pkg/services/notifications/notifications_test.go b/pkg/services/notifications/notifications_test.go -index e7680c3943..fb73e332ea 100644 ---- a/pkg/services/notifications/notifications_test.go -+++ b/pkg/services/notifications/notifications_test.go -@@ -1,12 +1,14 @@ - package notifications - - import ( -+ "regexp" - "testing" - - "github.com/grafana/grafana/pkg/bus" - "github.com/grafana/grafana/pkg/models" - "github.com/grafana/grafana/pkg/setting" - . "github.com/smartystreets/goconvey/convey" -+ "github.com/stretchr/testify/require" - ) - - func TestNotifications(t *testing.T) { -@@ -25,13 +27,28 @@ func TestNotifications(t *testing.T) { - So(err, ShouldBeNil) - - Convey("When sending reset email password", func() { -- err := ns.sendResetPasswordEmail(&models.SendResetPasswordEmailCommand{User: &models.User{Email: "asd@asd.com"}}) -+ user := models.User{Email: "asd@asd.com", Login: "asd@asd.com"} -+ err := ns.sendResetPasswordEmail(&models.SendResetPasswordEmailCommand{User: &user}) - So(err, ShouldBeNil) - - sentMsg := <-ns.mailQueue - So(sentMsg.Body, ShouldContainSubstring, "body") - So(sentMsg.Subject, ShouldEqual, "Reset your Grafana password - asd@asd.com") - So(sentMsg.Body, ShouldNotContainSubstring, "Subject") -+ -+ // find code in mail -+ r, _ := regexp.Compile(`code=(\w+)`) -+ match := r.FindString(sentMsg.Body) -+ code := match[len("code="):] -+ -+ // verify code -+ bus.AddHandler("test", func(query *models.GetUserByLoginQuery) error { -+ query.Result = &user -+ return nil -+ }) -+ query := models.ValidateResetPasswordCodeQuery{Code: code} -+ err = ns.validateResetPasswordCode(&query) -+ require.NoError(t, err) - }) - }) - } diff --git a/Makefile b/Makefile index dab531d..1a4ea40 100644 --- a/Makefile +++ b/Makefile @@ -13,11 +13,13 @@ WEBPACK_TAR := $(RPM_NAME)-webpack-$(VERSION)-$(RELEASE).tar.gz # - changes in Go module imports (which affect the vendored Go modules) PATCHES_PRE_VENDOR := \ 005-remove-unused-dependencies.patch \ - 008-remove-unused-frontend-crypto.patch + 008-remove-unused-frontend-crypto.patch \ + 012-support-go1.18.patch # patches which must be applied before creating the webpack, for example: # - changes in Node.js sources or vendored dependencies -PATCHES_PRE_WEBPACK := +PATCHES_PRE_WEBPACK := \ + 008-remove-unused-frontend-crypto.patch all: $(SOURCE_TAR) $(VENDOR_TAR) $(WEBPACK_TAR) diff --git a/grafana.spec b/grafana.spec index dbf2db5..2d65448 100644 --- a/grafana.spec +++ b/grafana.spec @@ -29,8 +29,8 @@ end} %endif Name: grafana -Version: 7.5.11 -Release: 4%{?dist} +Version: 7.5.13 +Release: 1%{?dist} Summary: Metrics dashboard and graph editor License: ASL 2.0 URL: https://grafana.org @@ -91,9 +91,9 @@ Patch9: 009-patch-unused-backend-crypto.patch # if FIPS mode is enabled. Patch10: 010-fips.patch -Patch11: 011-CVE-2021-43813.patch +Patch11: 011-use-hmac-sha-256-for-password-reset-tokens.patch -Patch12: 012-use-hmac-sha-256-for-password-reset-tokens.patch +Patch12: 012-support-go1.18.patch # Intersection of go_arches and nodejs_arches ExclusiveArch: %{grafana_arches} @@ -197,7 +197,7 @@ Provides: bundled(golang(github.com/inconshreveable/log15)) = 0.0.0-201808181646 Provides: bundled(golang(github.com/influxdata/influxdb-client-go/v2)) = 2.2.0 Provides: bundled(golang(github.com/jaegertracing/jaeger)) = 1.22.1-0.20210304164023.2fff3ca58910 Provides: bundled(golang(github.com/jmespath/go-jmespath)) = 0.4.0 -Provides: bundled(golang(github.com/json-iterator/go)) = 1.1.10 +Provides: bundled(golang(github.com/json-iterator/go)) = 1.1.12 Provides: bundled(golang(github.com/lib/pq)) = 1.9.0 Provides: bundled(golang(github.com/linkedin/goavro/v2)) = 2.10.0 Provides: bundled(golang(github.com/magefile/mage)) = 1.11.0 @@ -230,7 +230,7 @@ Provides: bundled(golang(golang.org/x/net)) = 0.0.0-20210119194325.5f4716e94777 Provides: bundled(golang(golang.org/x/oauth2)) = 0.0.0-20210113205817.d3ed898aa8a3 Provides: bundled(golang(golang.org/x/sync)) = 0.0.0-20201207232520.09787c993a3a Provides: bundled(golang(golang.org/x/time)) = 0.0.0-20200630173020.3af7569d3a1e -Provides: bundled(golang(gonum.org/v1/gonum)) = 0.8.2 +Provides: bundled(golang(gonum.org/v1/gonum)) = 0.9.1-0.20220120213227.d4eca1bbc084 Provides: bundled(golang(google.golang.org/api)) = 0.40.0 Provides: bundled(golang(google.golang.org/grpc)) = 1.36.0 Provides: bundled(golang(gopkg.in/ini.v1)) = 1.62.0 @@ -680,6 +680,10 @@ OPENSSL_FORCE_FIPS_MODE=1 GOLANG_FIPS=1 go test -v ./pkg/util -run TestEncryptio %changelog +* Fri Jan 28 2022 Andreas Gerstmayr 7.5.13-1 +- update to 7.5.13 tagged upstream community sources, see CHANGELOG +- support Go 1.18 + * Thu Jan 20 2022 Fedora Release Engineering - 7.5.11-4 - Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild diff --git a/sources b/sources index 2206676..df36ea7 100644 --- a/sources +++ b/sources @@ -1,3 +1,3 @@ -SHA512 (grafana-7.5.11.tar.gz) = 355af8d8cff77b2222d18559add8d2a16b086f6354d92b188d2f26fc0b7ffa04b877e568ddcfbc61b8786bdfdc59263009b727ef00b56438ef3fa9bbe6d80583 -SHA512 (grafana-webpack-7.5.11-1.tar.gz) = da927873d5797849783b02126e3f30384fd02ded604c3e50667a27ba1d1c9f9fb6a646ec48f1ac141ffd261c4132b303ffb970d84418332dae60f375258268f6 -SHA512 (grafana-vendor-7.5.11-1.tar.xz) = 37437f704ee51323d058072a63c3e8700268bf97dd58577a6f6c64b9c8f7ee35b120aa46ba8aea3854a088eddd6a59c24cee0232ecfa52de5b8c8cba5041a96e +SHA512 (grafana-7.5.13.tar.gz) = 25ae342cce34b4b120d278036ff2e515da74b62eec2283bb4c6f49e096163afaf6ae3ed3365e55d5a2b15d89db102724951948efdd876b2358d93cf0aac9a148 +SHA512 (grafana-webpack-7.5.13-1.tar.gz) = 5c5c45b548bca1a784f9de0c49aff5fb43d12a63b8c4a49639c34438cdc9b58e6cb5a0f027219b2090de5ba0ecc6919c813569ee4941e1c921843b77733079a7 +SHA512 (grafana-vendor-7.5.13-1.tar.xz) = 4418e4b4c2df70f1a13ef6f56b20e6afc12f10f57533877aa626313163de7e0766dc4f1b676d54e15c6063c6a4114e5360ecdce40be87561ff68e7f54bdc5da6