From 06cab9921d64d96cd2d431a8629eefb20befe483 Mon Sep 17 00:00:00 2001 From: Pavel Date: Wed, 30 Oct 2019 17:02:11 +0300 Subject: [PATCH 1/2] error collector (mostly for migrations) --- core/error_collector.go | 38 ++++++++++++++++++++++++++++++++++ core/error_collector_test.go | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 core/error_collector.go create mode 100644 core/error_collector_test.go diff --git a/core/error_collector.go b/core/error_collector.go new file mode 100644 index 0000000..f494ebb --- /dev/null +++ b/core/error_collector.go @@ -0,0 +1,38 @@ +package core + +import ( + "github.com/pkg/errors" +) + +// ErrorCollector can be used to group several error calls into one call. +// It is mostly useful in GORM migrations, where you should return only one errors, but several can occur. +// Error messages are composed into one message. For example: +// err := core.ErrorCollector( +// errors.New("first"), +// errors.New("second") +// ) +// +// // Will output `first < second` +// fmt.Println(err.Error()) +// Example with GORM migration, returns one migration error with all error messages: +// return core.ErrorCollector( +// db.CreateTable(models.Account{}, models.Connection{}).Error, +// db.Table("account").AddUniqueIndex("account_key", "channel").Error, +// ) +func ErrorCollector(errorsList ...error) error { + var errorMsg string + + for _, errItem := range errorsList { + if errItem == nil { + continue + } + + errorMsg += "< " + errItem.Error() + " " + } + + if errorMsg != "" { + return errors.New(errorMsg[2 : len(errorMsg)-1]) + } + + return nil +} diff --git a/core/error_collector_test.go b/core/error_collector_test.go new file mode 100644 index 0000000..5eaf6ea --- /dev/null +++ b/core/error_collector_test.go @@ -0,0 +1,40 @@ +package core + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestErrorCollector_NoError(t *testing.T) { + err := ErrorCollector(nil, nil, nil) + + assert.NoError(t, err) + assert.Nil(t, err) +} + +func TestErrorCollector_SeveralErrors(t *testing.T) { + err := ErrorCollector(nil, errors.New("error text"), nil) + + assert.Error(t, err) + assert.Equal(t, "error text", err.Error()) +} + +func TestErrorCollector_EmptyErrorMessage(t *testing.T) { + err := ErrorCollector(nil, errors.New(""), nil) + + assert.Error(t, err) + assert.Equal(t, "", err.Error()) +} + +func TestErrorCollector_AllErrors(t *testing.T) { + err := ErrorCollector( + errors.New("first"), + errors.New("second"), + errors.New("third"), + ) + + assert.Error(t, err) + assert.Equal(t, "first < second < third", err.Error()) +} From 3432853c8bd2e19517422304a77661b3f8e37780 Mon Sep 17 00:00:00 2001 From: Pavel Date: Thu, 31 Oct 2019 14:21:39 +0300 Subject: [PATCH 2/2] csrf middleware --- core/csrf.go | 233 +++++++++++++++++++++++++++++++++ core/csrf_test.go | 305 ++++++++++++++++++++++++++++++++++++++++++++ core/engine.go | 73 +++++++++++ core/engine_test.go | 109 ++++++++++++++++ go.mod | 3 +- go.sum | 36 +----- 6 files changed, 727 insertions(+), 32 deletions(-) create mode 100644 core/csrf.go create mode 100644 core/csrf_test.go diff --git a/core/csrf.go b/core/csrf.go new file mode 100644 index 0000000..9781689 --- /dev/null +++ b/core/csrf.go @@ -0,0 +1,233 @@ +package core + +import ( + "bytes" + "crypto/sha1" + "encoding/base64" + "io" + "io/ioutil" + "math/rand" + "time" + + "github.com/gin-gonic/gin" + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" +) + +// CSRFTokenGetter func type +type CSRFTokenGetter func(c *gin.Context) string + +// DefaultCSRFTokenGetter default getter +var DefaultCSRFTokenGetter = func(c *gin.Context) string { + r := c.Request + + if t := r.URL.Query().Get("csrf_token"); len(t) > 0 { + return t + } else if t := r.Header.Get("X-CSRF-Token"); len(t) > 0 { + return t + } else if t := r.Header.Get("X-XSRF-Token"); len(t) > 0 { + return t + } else if c.Request.Body != nil { + data, _ := ioutil.ReadAll(c.Request.Body) + c.Request.Body = ioutil.NopCloser(bytes.NewReader(data)) + t := r.FormValue("csrf_token") + c.Request.Body = ioutil.NopCloser(bytes.NewReader(data)) + + if len(t) > 0 { + return t + } + } + + return "" +} + +// DefaultIgnoredMethods ignored methods for CSRF verifier middleware +var DefaultIgnoredMethods = []string{"GET", "HEAD", "OPTIONS"} + +type CSRF struct { + salt string + secret string + sessionName string + abortFunc gin.HandlerFunc + csrfTokenGetter CSRFTokenGetter + store sessions.Store + locale *Localizer +} + +// NewCSRF creates CSRF struct with specified configuration and session store. +// GenerateCSRFMiddleware and VerifyCSRFMiddleware returns CSRF middlewares. +// Salt must be different every time (pass empty salt to use random), secret must be provided, sessionName is optional - pass empty to use default, +// store will be used to store sessions, abortFunc will be called to return error if token is invalid, csrfTokenGetter will be used to obtain token. +// Usage (with random salt): +// core.NewCSRF("", "super secret", "csrf_session", store, func (c *gin.Context) { +// c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid CSRF token"}) +// }, core.DefaultCSRFTokenGetter) +// Note for csrfTokenGetter: if you want to read token from request body (for example, from form field) - don't forget to restore Body data! +// Body in http.Request is io.ReadCloser instance. Reading CSRF token from form like that: +// if t := r.FormValue("csrf_token"); len(t) > 0 { +// return t +// } +// will close body - and all next middlewares won't be able to read body at all! +// Use DefaultCSRFTokenGetter as example to implement your own token getter. +func NewCSRF(salt, secret, sessionName string, store sessions.Store, abortFunc gin.HandlerFunc, csrfTokenGetter CSRFTokenGetter) *CSRF { + if store == nil { + panic("store must not be nil") + } + + if secret == "" { + panic("at least secret must be provided") + } + + csrf := &CSRF{ + store: store, + secret: secret, + abortFunc: abortFunc, + csrfTokenGetter: csrfTokenGetter, + } + + if salt == "" { + salt = csrf.generateSalt() + } + + if sessionName == "" { + sessionName = "csrf_token_session" + } + + csrf.salt = salt + csrf.sessionName = sessionName + + return csrf +} + +// strInSlice checks whether string exists in slice +func (x *CSRF) strInSlice(slice []string, v string) bool { + exists := false + + for _, i := range slice { + if i == v { + exists = true + break + } + } + + return exists +} + +// generateCSRFToken generates new CSRF token +func (x *CSRF) generateCSRFToken() string { + h := sha1.New() + io.WriteString(h, x.salt+"#"+x.secret) + hash := base64.URLEncoding.EncodeToString(h.Sum(nil)) + + return hash +} + +// generateSalt generates salt from random bytes. If it fails to generate cryptographically +// secure salt - it will generate pseudo-random, weaker salt. +// It will be used automatically if no salt provided. +// Default secure salt length: 8 bytes. +// Default pseudo-random salt length: 64 bytes. +func (x *CSRF) generateSalt() string { + salt := securecookie.GenerateRandomKey(8) + + if salt == nil { + return x.pseudoRandomString(64) + } + + return string(salt) +} + +// pseudoRandomString generates pseudo-random string with specified length +func (x *CSRF) pseudoRandomString(length int) string { + rand.Seed(time.Now().UnixNano()) + data := make([]byte, length) + + for i := 0; i < length; i++ { + data[i] = byte(65 + rand.Intn(90-65)) + } + + return string(data) +} + +// CSRFFromContext returns csrf token or random token. It shouldn't return empty string because it will make csrf protection useless. +// e.g. any request without token will work fine, which is inacceptable. +func (x *CSRF) CSRFFromContext(c *gin.Context) string { + if i, ok := c.Get("csrf_token"); ok { + if token, ok := i.(string); ok { + return token + } else { + return x.generateCSRFToken() + } + } else { + return x.generateCSRFToken() + } +} + +// GenerateCSRFMiddleware returns gin.HandlerFunc which will generate CSRF token +// Usage: +// engine := gin.New() +// csrf := NewCSRF("salt", "secret", "not_found", "incorrect", localizer) +// engine.Use(csrf.GenerateCSRFMiddleware()) +func (x *CSRF) GenerateCSRFMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + session, _ := x.store.Get(c.Request, x.sessionName) + + if i, ok := session.Values["csrf_token"]; ok { + if i, ok := i.(string); !ok || i == "" { + if x.fillToken(session, c) != nil { + x.abortFunc(c) + c.Abort() + return + } + } + } else { + if x.fillToken(session, c) != nil { + x.abortFunc(c) + c.Abort() + return + } + } + } +} + +// fillToken stores token in session and context +func (x *CSRF) fillToken(s *sessions.Session, c *gin.Context) error { + s.Values["csrf_token"] = x.generateCSRFToken() + c.Set("csrf_token", s.Values["csrf_token"]) + return s.Save(c.Request, c.Writer) +} + +// VerifyCSRFMiddleware verifies CSRF token +// Usage: +// engine := gin.New() +// engine.Use(csrf.VerifyCSRFMiddleware()) +func (x *CSRF) VerifyCSRFMiddleware(ignoredMethods []string) gin.HandlerFunc { + return func(c *gin.Context) { + if x.strInSlice(ignoredMethods, c.Request.Method) { + return + } + + var token string + session, _ := x.store.Get(c.Request, x.sessionName) + + if i, ok := session.Values["csrf_token"]; ok { + if i, ok := i.(string); !ok || i == "" { + x.abortFunc(c) + c.Abort() + return + } else { + token = i + } + } else { + x.abortFunc(c) + c.Abort() + return + } + + if x.csrfTokenGetter(c) != token { + x.abortFunc(c) + c.Abort() + return + } + } +} diff --git a/core/csrf_test.go b/core/csrf_test.go new file mode 100644 index 0000000..ec5556c --- /dev/null +++ b/core/csrf_test.go @@ -0,0 +1,305 @@ +package core + +import ( + "bytes" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/gin-gonic/gin" + "github.com/gorilla/sessions" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type CSRFTest struct { + suite.Suite + csrf *CSRF +} + +type requestOptions struct { + Method string + URL string + Headers map[string]string + Body io.Reader +} + +func TestCSRF_DefaultCSRFTokenGetter_Empty(t *testing.T) { + c := &gin.Context{Request: &http.Request{ + URL: &url.URL{ + RawQuery: "", + }, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }} + + assert.Empty(t, DefaultCSRFTokenGetter(c)) +} + +func TestCSRF_DefaultCSRFTokenGetter_URL(t *testing.T) { + c := &gin.Context{Request: &http.Request{ + URL: &url.URL{ + RawQuery: "csrf_token=token", + }, + }} + + assert.NotEmpty(t, DefaultCSRFTokenGetter(c)) + assert.Equal(t, "token", DefaultCSRFTokenGetter(c)) +} + +func TestCSRF_DefaultCSRFTokenGetter_Header_CSRF(t *testing.T) { + header := http.Header{} + header.Add("X-CSRF-Token", "token") + c := &gin.Context{Request: &http.Request{ + URL: &url.URL{ + RawQuery: "", + }, + Header: header, + }} + + assert.NotEmpty(t, DefaultCSRFTokenGetter(c)) + assert.Equal(t, "token", DefaultCSRFTokenGetter(c)) +} + +func TestCSRF_DefaultCSRFTokenGetter_Header_XSRC(t *testing.T) { + header := http.Header{} + header.Add("X-XSRF-Token", "token") + c := &gin.Context{Request: &http.Request{ + URL: &url.URL{ + RawQuery: "", + }, + Header: header, + }} + + assert.NotEmpty(t, DefaultCSRFTokenGetter(c)) + assert.Equal(t, "token", DefaultCSRFTokenGetter(c)) +} + +func TestCSRF_DefaultCSRFTokenGetter_Form(t *testing.T) { + headers := http.Header{} + headers.Add("Content-Type", "application/x-www-form-urlencoded") + c := &gin.Context{Request: &http.Request{ + URL: &url.URL{ + RawQuery: "", + }, + Header: headers, + Body: ioutil.NopCloser(bytes.NewReader([]byte(""))), + }} + c.Request.PostForm = url.Values{"csrf_token": {"token"}} + + assert.NotEmpty(t, DefaultCSRFTokenGetter(c)) + assert.Equal(t, "token", DefaultCSRFTokenGetter(c)) + + _, err := ioutil.ReadAll(c.Request.Body) + assert.NoError(t, err) +} + +func TestCSRF_NewCSRF_NilStore(t *testing.T) { + defer func() { + assert.NotNil(t, recover()) + }() + + NewCSRF("salt", "secret", "csrf", nil, func(context *gin.Context) {}, DefaultCSRFTokenGetter) +} + +func TestCSRF_NewCSRF_EmptySecret(t *testing.T) { + defer func() { + assert.NotNil(t, recover()) + }() + + store := sessions.NewCookieStore([]byte("keys")) + NewCSRF("salt", "", "csrf", store, func(context *gin.Context) {}, DefaultCSRFTokenGetter) +} + +func TestCSRF_NewCSRF_SaltAndSessionNotEmpty(t *testing.T) { + store := sessions.NewCookieStore([]byte("keys")) + csrf := NewCSRF("salt", "secret", "", store, func(context *gin.Context) {}, DefaultCSRFTokenGetter) + assert.NotEmpty(t, csrf.salt) + assert.NotEmpty(t, csrf.sessionName) +} + +func TestCSRF_Suite(t *testing.T) { + suite.Run(t, new(CSRFTest)) +} + +func (x *CSRFTest) SetupSuite() { + store := sessions.NewCookieStore([]byte("keys")) + x.csrf = NewCSRF("salt", "secret", "", store, func(context *gin.Context) { + context.AbortWithStatus(900) + }, DefaultCSRFTokenGetter) +} + +func (x *CSRFTest) NewServer() *gin.Engine { + gin.SetMode(gin.TestMode) + g := gin.New() + g.Use(x.csrf.GenerateCSRFMiddleware(), x.csrf.VerifyCSRFMiddleware(DefaultIgnoredMethods)) + return g +} + +func (x *CSRFTest) request(server *gin.Engine, options requestOptions) (*httptest.ResponseRecorder, *http.Request) { + if options.Method == "" { + options.Method = "GET" + } + + w := httptest.NewRecorder() + req, err := http.NewRequest(options.Method, options.URL, options.Body) + + if options.Headers != nil { + for key, value := range options.Headers { + req.Header.Set(key, value) + } + } + + server.ServeHTTP(w, req) + + if err != nil { + panic(err) + } + + return w, req +} + +func (x *CSRFTest) Test_strInSlice() { + slice := []string{"alpha", "beta", "gamma"} + + assert.False(x.T(), x.csrf.strInSlice(slice, "lambda")) + assert.True(x.T(), x.csrf.strInSlice(slice, "alpha")) +} + +func (x *CSRFTest) Test_generateCSRFToken() { + assert.NotEmpty(x.T(), x.csrf.generateCSRFToken()) +} + +func (x *CSRFTest) Test_generateSalt() { + salt := x.csrf.generateSalt() + assert.NotEmpty(x.T(), salt) +} + +func (x *CSRFTest) Test_pseudoRandomString() { + assert.Len(x.T(), x.csrf.pseudoRandomString(12), 12) + assert.Len(x.T(), x.csrf.pseudoRandomString(64), 64) +} + +func (x *CSRFTest) Test_CSRFFromContext_NotExist() { + c := &gin.Context{} + token := x.csrf.CSRFFromContext(c) + + assert.NotEmpty(x.T(), token) +} + +func (x *CSRFTest) Test_CSRFFromContext_NotString() { + c := &gin.Context{} + c.Set("csrf_token", struct{}{}) + token := x.csrf.CSRFFromContext(c) + + assert.NotEmpty(x.T(), token) +} + +func (x *CSRFTest) Test_CSRFFromContext_Exist() { + c := &gin.Context{} + c.Set("csrf_token", "token") + token := x.csrf.CSRFFromContext(c) + + assert.NotEmpty(x.T(), token) + assert.Equal(x.T(), "token", token) +} + +func (x *CSRFTest) Test_GenerateCSRFMiddleware() { + assert.NotNil(x.T(), x.csrf.GenerateCSRFMiddleware()) +} + +func (x *CSRFTest) Test_GenerateCSRFMiddleware_Middleware() { + x.csrf.store = sessions.NewCookieStore([]byte("secret")) + g := x.NewServer() + g.GET("/get", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + g.POST("/post", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + + get, getReq := x.request(g, requestOptions{ + Method: "GET", + URL: "/get", + }) + + session, _ := x.csrf.store.Get(getReq, x.csrf.sessionName) + post, _ := x.request(g, requestOptions{ + Method: "POST", + URL: "/post", + Headers: map[string]string{ + "Cookie": get.Header().Get("Set-Cookie"), + "X-CSRF-Token": session.Values["csrf_token"].(string), + }, + }) + + getWithToken, getReqWithToken := x.request(g, requestOptions{ + Method: "GET", + URL: "/get", + Headers: map[string]string{ + "Cookie": get.Header().Get("Set-Cookie"), + "X-CSRF-Token": session.Values["csrf_token"].(string), + }, + }) + + secondSession, _ := x.csrf.store.Get(getReqWithToken, x.csrf.sessionName) + + assert.Equal(x.T(), session.Values["csrf_token"].(string), secondSession.Values["csrf_token"].(string)) + assert.Equal(x.T(), "OK", get.Body.String()) + assert.Equal(x.T(), http.StatusOK, get.Result().StatusCode) + assert.Equal(x.T(), "OK", getWithToken.Body.String()) + assert.Equal(x.T(), http.StatusOK, getWithToken.Result().StatusCode) + assert.Equal(x.T(), "OK", post.Body.String()) + assert.Equal(x.T(), http.StatusOK, post.Result().StatusCode) +} + +func (x *CSRFTest) Test_VerifyCSRFMiddleware_NoToken() { + x.csrf.store = sessions.NewCookieStore([]byte("secret")) + g := x.NewServer() + g.GET("/get", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + g.POST("/post", func(c *gin.Context) { + c.String(http.StatusOK, "OK") + }) + + postWithoutSession, _ := x.request(g, requestOptions{ + Method: "POST", + URL: "/post", + }) + + get, getReq := x.request(g, requestOptions{ + Method: "GET", + URL: "/get", + }) + + session, _ := x.csrf.store.Get(getReq, x.csrf.sessionName) + post, _ := x.request(g, requestOptions{ + Method: "POST", + URL: "/post", + Headers: map[string]string{ + "Cookie": get.Header().Get("Set-Cookie"), + "X-CSRF-Token": session.Values["csrf_token"].(string), + }, + }) + + postIncorrectToken, _ := x.request(g, requestOptions{ + Method: "POST", + URL: "/post", + Headers: map[string]string{ + "Cookie": get.Header().Get("Set-Cookie"), + "X-CSRF-Token": "incorrect token", + }, + }) + + assert.NotEqual(x.T(), "OK", postWithoutSession.Body.String()) + assert.Equal(x.T(), 900, postWithoutSession.Result().StatusCode) + assert.NotEqual(x.T(), "OK", postIncorrectToken.Body.String()) + assert.Equal(x.T(), 900, postIncorrectToken.Result().StatusCode) + assert.Equal(x.T(), "OK", get.Body.String()) + assert.Equal(x.T(), http.StatusOK, get.Result().StatusCode) + assert.Equal(x.T(), "OK", post.Body.String()) + assert.Equal(x.T(), http.StatusOK, post.Result().StatusCode) +} diff --git a/core/engine.go b/core/engine.go index 057809d..f13d34f 100644 --- a/core/engine.go +++ b/core/engine.go @@ -6,6 +6,8 @@ import ( "github.com/gin-gonic/gin" "github.com/gobuffalo/packr/v2" + "github.com/gorilla/securecookie" + "github.com/gorilla/sessions" "github.com/op/go-logging" ) @@ -18,6 +20,8 @@ type Engine struct { ginEngine *gin.Engine httpClient *http.Client Logger *logging.Logger + csrf *CSRF + Sessions sessions.Store Config ConfigInterface LogFormatter logging.Formatter prepared bool @@ -157,6 +161,75 @@ func (e *Engine) HTTPClient() *http.Client { } } +// WithCookieSessions generates new CookieStore with optional key length. +// Default key length is 32 bytes. +func (e *Engine) WithCookieSessions(keyLength ...int) *Engine { + length := 32 + + if len(keyLength) > 0 && keyLength[0] > 0 { + length = keyLength[0] + } + + e.Sessions = sessions.NewCookieStore(securecookie.GenerateRandomKey(length)) + return e +} + +// WithCookieSessions generates new FilesystemStore with optional key length. +// Default key length is 32 bytes. +func (e *Engine) WithFilesystemSessions(path string, keyLength ...int) *Engine { + length := 32 + + if len(keyLength) > 0 && keyLength[0] > 0 { + length = keyLength[0] + } + + e.Sessions = sessions.NewFilesystemStore(path, securecookie.GenerateRandomKey(length)) + return e +} + +// InitCSRF initializes CSRF middleware. engine.Sessions must be already initialized, +// use engine.WithCookieStore or engine.WithFilesystemStore for that. +// Syntax is similar to core.NewCSRF, but you shouldn't pass sessionName, store and salt. +func (e *Engine) InitCSRF(secret string, abortFunc gin.HandlerFunc, getter CSRFTokenGetter) *Engine { + if e.Sessions == nil { + panic("engine.Sessions must be initialized first") + } + + e.csrf = NewCSRF("", secret, "", e.Sessions, abortFunc, getter) + return e +} + +// VerifyCSRFMiddleware returns CSRF verifier middleware +// Usage: +// engine.Router().Use(engine.VerifyCSRFMiddleware(core.DefaultIgnoredMethods)) +func (e *Engine) VerifyCSRFMiddleware(ignoredMethods []string) gin.HandlerFunc { + if e.csrf == nil { + panic("csrf is not initialized") + } + + return e.csrf.VerifyCSRFMiddleware(ignoredMethods) +} + +// GenerateCSRFMiddleware returns CSRF generator middleware +// Usage: +// engine.Router().Use(engine.GenerateCSRFMiddleware()) +func (e *Engine) GenerateCSRFMiddleware() gin.HandlerFunc { + if e.csrf == nil { + panic("csrf is not initialized") + } + + return e.csrf.GenerateCSRFMiddleware() +} + +// GetCSRFToken returns CSRF token from provided context +func (e *Engine) GetCSRFToken(c *gin.Context) string { + if e.csrf == nil { + panic("csrf is not initialized") + } + + return e.csrf.CSRFFromContext(c) +} + // ConfigureRouter will call provided callback with current gin.Engine, or panic if engine is not present func (e *Engine) ConfigureRouter(callback func(*gin.Engine)) *Engine { callback(e.Router()) diff --git a/core/engine_test.go b/core/engine_test.go index da87fdf..ae2cc57 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -1,9 +1,12 @@ package core import ( + "bytes" "database/sql" "html/template" "io/ioutil" + "net/http" + "net/url" "os" "testing" @@ -180,6 +183,112 @@ func (e *EngineTest) Test_HTTPClient() { assert.NotNil(e.T(), e.engine.httpClient) } +func (e *EngineTest) Test_WithCookieSessions() { + e.engine.Sessions = nil + e.engine.WithCookieSessions(4) + + assert.NotNil(e.T(), e.engine.Sessions) +} + +func (e *EngineTest) Test_WithFilesystemSessions() { + e.engine.Sessions = nil + e.engine.WithFilesystemSessions(os.TempDir(), 4) + + assert.NotNil(e.T(), e.engine.Sessions) +} + +func (e *EngineTest) Test_InitCSRF_Fail() { + defer func() { + assert.NotNil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.Sessions = nil + e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) + assert.Nil(e.T(), e.engine.csrf) +} + +func (e *EngineTest) Test_InitCSRF() { + defer func() { + assert.Nil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.WithCookieSessions(4) + e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) + assert.NotNil(e.T(), e.engine.csrf) +} + +func (e *EngineTest) Test_VerifyCSRFMiddleware_Fail() { + defer func() { + assert.NotNil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.VerifyCSRFMiddleware(DefaultIgnoredMethods) +} + +func (e *EngineTest) Test_VerifyCSRFMiddleware() { + defer func() { + assert.Nil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.WithCookieSessions(4) + e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) + e.engine.VerifyCSRFMiddleware(DefaultIgnoredMethods) +} + +func (e *EngineTest) Test_GenerateCSRFMiddleware_Fail() { + defer func() { + assert.NotNil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.GenerateCSRFMiddleware() +} + +func (e *EngineTest) Test_GenerateCSRFMiddleware() { + defer func() { + assert.Nil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.WithCookieSessions(4) + e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) + e.engine.GenerateCSRFMiddleware() +} + +func (e *EngineTest) Test_GetCSRFToken_Fail() { + defer func() { + assert.NotNil(e.T(), recover()) + }() + + e.engine.csrf = nil + e.engine.GetCSRFToken(nil) +} + +func (e *EngineTest) Test_GetCSRFToken() { + defer func() { + assert.Nil(e.T(), recover()) + }() + + c := &gin.Context{Request: &http.Request{ + URL: &url.URL{ + RawQuery: "", + }, + Body: ioutil.NopCloser(bytes.NewReader([]byte{})), + Header: http.Header{"X-CSRF-Token": []string{"token"}}, + }} + c.Set("csrf_token", "token") + + e.engine.csrf = nil + e.engine.WithCookieSessions(4) + e.engine.InitCSRF("test", func(context *gin.Context) {}, DefaultCSRFTokenGetter) + assert.NotEmpty(e.T(), e.engine.GetCSRFToken(c)) + assert.Equal(e.T(), "token", e.engine.GetCSRFToken(c)) +} + func (e *EngineTest) Test_Run_Fail() { defer func() { assert.NotNil(e.T(), recover()) diff --git a/go.mod b/go.mod index 893a6e6..305b211 100644 --- a/go.mod +++ b/go.mod @@ -15,11 +15,12 @@ require ( github.com/gobuffalo/packr/v2 v2.7.1 github.com/golang/protobuf v1.3.2 // indirect github.com/google/go-querystring v1.0.0 // indirect + github.com/gorilla/securecookie v1.1.1 + github.com/gorilla/sessions v1.2.0 github.com/h2non/gock v1.0.10 github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/gorm v1.9.11 github.com/json-iterator/go v1.1.7 // indirect - github.com/karrick/godirwalk v1.12.0 // indirect github.com/lib/pq v1.2.0 // indirect github.com/mattn/go-isatty v0.0.10 // indirect github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 // indirect diff --git a/go.sum b/go.sum index 95a231f..c48871c 100644 --- a/go.sum +++ b/go.sum @@ -15,13 +15,9 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/aws/aws-sdk-go v1.23.9 h1:UYWPGrBMlrW5VCYeWMbog1T/kqZzkvvheUDQaaUAhqI= -github.com/aws/aws-sdk-go v1.23.9/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.14 h1:hEsU+cukBOQe1wRRuvEgG+y6AVCyS2eyHWuTefhGxTY= github.com/aws/aws-sdk-go v1.25.14/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713 h1:UNOqI3EKhvbqV8f1Vm3NIwkrhq388sGCeAH2Op7w0rc= -github.com/certifi/gocertifi v0.0.0-20190506164543-d2eda7129713/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 h1:8k9FLYBLKT+9v2HQJ/a95ZemmTx+/ltJcAiRhVushG8= github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -43,19 +39,13 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03 h1:G/9fPivTr5EiyqE9OlW65iMRUxFXMGRHgZFGo50uG8Q= -github.com/getsentry/raven-go v0.0.0-20180903072508-084a9de9eb03/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce h1:KqeVCdb+M2iwyF6GzdYxTazfE1cE+133RXuGaZ5Sc1E= -github.com/gin-contrib/multitemplate v0.0.0-20180827023943-5799bbbb6dce/go.mod h1:62qM8p4crGvNKE413gTzn4eMFin1VOJfMDWMRzHdvqM= github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec h1:mfeHJfPwsXp/iovjrTwxtkTMRAIXZu6Uxg6O95En1+Y= github.com/gin-contrib/multitemplate v0.0.0-20190914010127-bba2ccfe37ec/go.mod h1:2tmLQ8sVzr2XKwquGd7zNq3zB6fGyjJL+47JoxoF8yM= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs= -github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= github.com/gin-gonic/gin v1.4.0 h1:3tMoCCfM7ppqsR0ptz/wi1impNpT7/9wQtMZ8lr1mCQ= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -67,14 +57,10 @@ github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= github.com/gobuffalo/envy v1.7.1 h1:OQl5ys5MBea7OGCdvPbBJWRgnhC/fGona6QKfvFeau8= github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= -github.com/gobuffalo/logger v1.0.0 h1:xw9Ko9EcC5iAFprrjJ6oZco9UpzS5MQ4jAwghsLHdy4= -github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/logger v1.0.1 h1:ZEgyRGgAm4ZAhAO45YXMs5Fp+bzGLESFewzAVBMKuTg= github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= -github.com/gobuffalo/packr/v2 v2.6.0 h1:EMUzJIb5rof6r087PtGmgdzdLKpRBESJ/8jyL9MexfY= -github.com/gobuffalo/packr/v2 v2.6.0/go.mod h1:sgEE1xNZ6G0FNN5xn9pevVu4nywaxHvgup67xisti08= github.com/gobuffalo/packr/v2 v2.7.1 h1:n3CIW5T17T8v4GGK5sWXLVWJhCz7b5aNLSxW6gYim4o= github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -99,6 +85,10 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/h2non/gock v1.0.10 h1:EzHYzKKSLN4xk0w193uAy3tp8I3+L1jmaI2Mjg4lCgU= github.com/h2non/gock v1.0.10/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -109,8 +99,6 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v1.9.2 h1:lCvgEaqe/HVE+tjAR2mt4HbbHAZsQOv3XAZiEZV37iw= github.com/jinzhu/gorm v1.9.2/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo= -github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac= -github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY= github.com/jinzhu/gorm v1.9.11 h1:gaHGvE+UnWGlbWG4Y3FUwY1EcZ5n6S9WtqBA/uySMLE= github.com/jinzhu/gorm v1.9.11/go.mod h1:bu/pK8szGZ2puuErfU0RwyeNdsf3e6nCX/noXaVxkfw= github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= @@ -128,9 +116,6 @@ github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62F github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/karrick/godirwalk v1.10.12 h1:BqUm+LuJcXjGv1d2mj3gBiQyrQ57a0rYoAmhvJQ7RDU= -github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/karrick/godirwalk v1.12.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= @@ -147,8 +132,6 @@ github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= @@ -168,8 +151,6 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= -github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE= -github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU= github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY= github.com/nicksnyder/go-i18n/v2 v2.0.2/go.mod h1:JXS4+OKhbcwDoVTEj0sLFWL1vOwec2g/YBAxZ9owJqY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -193,12 +174,8 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/retailcrm/api-client-go v1.1.1 h1:yqsyYjBDdmDwExVlTdGucY9/IpEokXpkfTfA6z5AZ7M= -github.com/retailcrm/api-client-go v1.1.1/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c= github.com/retailcrm/api-client-go v1.3.0 h1:8cEtLZ9gk+nTEVuzA/wzQhb8tRfaxfCQHLdPtO+/gek= github.com/retailcrm/api-client-go v1.3.0/go.mod h1:QRoPE2SM6ST7i2g0yEdqm7Iw98y7cYuq3q14Ot+6N8c= -github.com/retailcrm/mg-transport-api-client-go v1.1.31 h1:21pE1JhT49rvbMLDYJa0iiqbb/roz+eSp27fPck4uUw= -github.com/retailcrm/mg-transport-api-client-go v1.1.31/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI= github.com/retailcrm/mg-transport-api-client-go v1.1.32 h1:IBPltSoD5q2PPZJbNC/prK5F9rEVPXVx/ZzDpi7HKhs= github.com/retailcrm/mg-transport-api-client-go v1.1.32/go.mod h1:AWV6BueE28/6SCoyfKURTo4lF0oXYoOKmHTzehd5vAI= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= @@ -268,6 +245,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -281,12 +259,9 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To= @@ -300,7 +275,6 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=