mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-22 16:48:30 +01:00
Merge pull request #10937 from reasonerjt/csrf-2.0
Update CSRF mechanism
This commit is contained in:
commit
6d89553c4d
@ -4,6 +4,3 @@ enablegzip = true
|
|||||||
|
|
||||||
[dev]
|
[dev]
|
||||||
httpport = 8080
|
httpport = 8080
|
||||||
EnableXSRF = true
|
|
||||||
XSRFKey = {{xsrf_key}}
|
|
||||||
XSRFExpire = 3600
|
|
||||||
|
@ -47,6 +47,7 @@ REGISTRY_CONTROLLER_URL={{registry_controller_url}}
|
|||||||
WITH_CHARTMUSEUM={{with_chartmuseum}}
|
WITH_CHARTMUSEUM={{with_chartmuseum}}
|
||||||
REGISTRY_CREDENTIAL_USERNAME={{registry_username}}
|
REGISTRY_CREDENTIAL_USERNAME={{registry_username}}
|
||||||
REGISTRY_CREDENTIAL_PASSWORD={{registry_password}}
|
REGISTRY_CREDENTIAL_PASSWORD={{registry_password}}
|
||||||
|
CSRF_KEY={{csrf_key}}
|
||||||
|
|
||||||
HTTP_PROXY={{core_http_proxy}}
|
HTTP_PROXY={{core_http_proxy}}
|
||||||
HTTPS_PROXY={{core_https_proxy}}
|
HTTPS_PROXY={{core_https_proxy}}
|
||||||
|
@ -32,14 +32,14 @@ def prepare_core(config_dict, with_notary, with_clair, with_trivy, with_chartmus
|
|||||||
with_clair=with_clair,
|
with_clair=with_clair,
|
||||||
with_trivy=with_trivy,
|
with_trivy=with_trivy,
|
||||||
with_chartmuseum=with_chartmuseum,
|
with_chartmuseum=with_chartmuseum,
|
||||||
|
csrf_key=generate_random_string(32),
|
||||||
**config_dict)
|
**config_dict)
|
||||||
|
|
||||||
render_jinja(
|
render_jinja(
|
||||||
core_conf_template_path,
|
core_conf_template_path,
|
||||||
core_conf,
|
core_conf,
|
||||||
uid=DEFAULT_UID,
|
uid=DEFAULT_UID,
|
||||||
gid=DEFAULT_GID,
|
gid=DEFAULT_GID)
|
||||||
xsrf_key=generate_random_string(40))
|
|
||||||
|
|
||||||
|
|
||||||
def copy_core_config(core_templates_path, core_config_path):
|
def copy_core_config(core_templates_path, core_config_path):
|
||||||
|
@ -83,10 +83,6 @@ func (b *BaseController) Prepare() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.ProjectMgr = pm
|
b.ProjectMgr = pm
|
||||||
|
|
||||||
if !filter.ReqCarriesSession(b.Ctx.Request) {
|
|
||||||
b.EnableXSRF = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequireAuthenticated returns true when the request is authenticated
|
// RequireAuthenticated returns true when the request is authenticated
|
||||||
@ -96,7 +92,6 @@ func (b *BaseController) RequireAuthenticated() bool {
|
|||||||
b.SendError(internal_errors.UnauthorizedError(errors.New("Unauthorized")))
|
b.SendError(internal_errors.UnauthorizedError(errors.New("Unauthorized")))
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ package controllers
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/goharbor/harbor/src/core/middlewares"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
@ -38,7 +39,6 @@ func init() {
|
|||||||
dir := filepath.Dir(file)
|
dir := filepath.Dir(file)
|
||||||
dir = filepath.Join(dir, "..")
|
dir = filepath.Join(dir, "..")
|
||||||
apppath, _ := filepath.Abs(dir)
|
apppath, _ := filepath.Abs(dir)
|
||||||
beego.BConfig.WebConfig.EnableXSRF = true
|
|
||||||
beego.BConfig.WebConfig.Session.SessionOn = true
|
beego.BConfig.WebConfig.Session.SessionOn = true
|
||||||
beego.TestBeegoInit(apppath)
|
beego.TestBeegoInit(apppath)
|
||||||
beego.AddTemplateExt("htm")
|
beego.AddTemplateExt("htm")
|
||||||
@ -100,30 +100,38 @@ func TestRedirectForOIDC(t *testing.T) {
|
|||||||
func TestAll(t *testing.T) {
|
func TestAll(t *testing.T) {
|
||||||
config.InitWithSettings(utilstest.GetUnitTestConfig())
|
config.InitWithSettings(utilstest.GetUnitTestConfig())
|
||||||
assert := assert.New(t)
|
assert := assert.New(t)
|
||||||
|
handler := http.Handler(beego.BeeApp.Handlers)
|
||||||
|
mws := middlewares.MiddleWares()
|
||||||
|
for i := len(mws) - 1; i >= 0; i-- {
|
||||||
|
if mws[i] == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
handler = mws[i](handler)
|
||||||
|
}
|
||||||
|
|
||||||
r, _ := http.NewRequest("POST", "/c/login", nil)
|
r, _ := http.NewRequest("POST", "/c/login", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
assert.Equal(http.StatusUnprocessableEntity, w.Code, "'/c/login' httpStatusCode should be 422")
|
assert.Equal(http.StatusForbidden, w.Code, "'/c/login' httpStatusCode should be 403")
|
||||||
|
|
||||||
r, _ = http.NewRequest("GET", "/c/log_out", nil)
|
r, _ = http.NewRequest("GET", "/c/log_out", nil)
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
assert.Equal(int(200), w.Code, "'/c/log_out' httpStatusCode should be 200")
|
assert.Equal(int(200), w.Code, "'/c/log_out' httpStatusCode should be 200")
|
||||||
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), ""), "http respond should be empty")
|
assert.Equal(true, strings.Contains(fmt.Sprintf("%s", w.Body), ""), "http respond should be empty")
|
||||||
|
|
||||||
r, _ = http.NewRequest("POST", "/c/reset", nil)
|
r, _ = http.NewRequest("POST", "/c/reset", nil)
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
assert.Equal(http.StatusUnprocessableEntity, w.Code, "'/c/reset' httpStatusCode should be 422")
|
assert.Equal(http.StatusForbidden, w.Code, "'/c/reset' httpStatusCode should be 403")
|
||||||
|
|
||||||
r, _ = http.NewRequest("POST", "/c/userExists", nil)
|
r, _ = http.NewRequest("POST", "/c/userExists", nil)
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
assert.Equal(http.StatusUnprocessableEntity, w.Code, "'/c/userExists' httpStatusCode should be 422")
|
assert.Equal(http.StatusForbidden, w.Code, "'/c/userExists' httpStatusCode should be 403")
|
||||||
|
|
||||||
r, _ = http.NewRequest("GET", "/c/sendEmail", nil)
|
r, _ = http.NewRequest("GET", "/c/sendEmail", nil)
|
||||||
w = httptest.NewRecorder()
|
w = httptest.NewRecorder()
|
||||||
beego.BeeApp.Handlers.ServeHTTP(w, r)
|
handler.ServeHTTP(w, r)
|
||||||
assert.Equal(int(400), w.Code, "'/c/sendEmail' httpStatusCode should be 400")
|
assert.Equal(int(400), w.Code, "'/c/sendEmail' httpStatusCode should be 400")
|
||||||
}
|
}
|
||||||
|
@ -18,11 +18,8 @@ func SessionCheck(ctx *beegoctx.Context) {
|
|||||||
req := ctx.Request
|
req := ctx.Request
|
||||||
_, err := req.Cookie(config.SessionCookieName)
|
_, err := req.Cookie(config.SessionCookieName)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// This is a temp workaround for beego bug: https://github.com/goharbor/harbor/issues/10446
|
ctx.Request = req.WithContext(context.WithValue(req.Context(), SessionReqKey, true))
|
||||||
// After we upgrading beego to the latest version and moving the filter to middleware,
|
log.Debugf("Mark the request as with-session: %s %s", req.Method, req.URL.RawPath)
|
||||||
// this workaround can be removed
|
|
||||||
*(ctx.Request) = *(req.WithContext(context.WithValue(req.Context(), SessionReqKey, true)))
|
|
||||||
log.Debug("Mark the request as no-session")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/server/middleware/csrf"
|
||||||
"github.com/goharbor/harbor/src/server/middleware/readonly"
|
"github.com/goharbor/harbor/src/server/middleware/readonly"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
@ -71,6 +72,7 @@ func legacyAPISkipper(r *http.Request) bool {
|
|||||||
// MiddleWares returns global middlewares
|
// MiddleWares returns global middlewares
|
||||||
func MiddleWares() []beego.MiddleWare {
|
func MiddleWares() []beego.MiddleWare {
|
||||||
return []beego.MiddleWare{
|
return []beego.MiddleWare{
|
||||||
|
csrf.Middleware(),
|
||||||
requestid.Middleware(),
|
requestid.Middleware(),
|
||||||
readonly.Middleware(readonlySkippers...),
|
readonly.Middleware(readonlySkippers...),
|
||||||
orm.Middleware(legacyAPISkipper),
|
orm.Middleware(legacyAPISkipper),
|
||||||
|
@ -6,8 +6,3 @@ import "github.com/goharbor/harbor/src/core/api"
|
|||||||
type BaseHandler struct {
|
type BaseHandler struct {
|
||||||
api.BaseController
|
api.BaseController
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare disable the xsrf as the request is from other components and do not require the xsrf token
|
|
||||||
func (bh *BaseHandler) Prepare() {
|
|
||||||
bh.EnableXSRF = false
|
|
||||||
}
|
|
||||||
|
@ -27,13 +27,6 @@ type Handler struct {
|
|||||||
beego.Controller
|
beego.Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare disables xsrf for /service/token endpoint.
|
|
||||||
// This is done on purpose b/c containerd will try to send POST and fallback to GET
|
|
||||||
// more details see #10305
|
|
||||||
func (h *Handler) Prepare() {
|
|
||||||
h.EnableXSRF = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get handles GET request, it checks the http header for user credentials
|
// Get handles GET request, it checks the http header for user credentials
|
||||||
// and parse service and scope based on docker registry v2 standard,
|
// and parse service and scope based on docker registry v2 standard,
|
||||||
// checks the permission against local DB and generates jwt token.
|
// checks the permission against local DB and generates jwt token.
|
||||||
|
@ -44,6 +44,7 @@ require (
|
|||||||
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
github.com/google/certificate-transparency-go v1.0.21 // indirect
|
||||||
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
|
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
|
||||||
github.com/google/uuid v1.1.1
|
github.com/google/uuid v1.1.1
|
||||||
|
github.com/gorilla/csrf v1.6.2
|
||||||
github.com/gorilla/handlers v1.3.0
|
github.com/gorilla/handlers v1.3.0
|
||||||
github.com/gorilla/mux v1.7.2
|
github.com/gorilla/mux v1.7.2
|
||||||
github.com/graph-gophers/dataloader v5.0.0+incompatible
|
github.com/graph-gophers/dataloader v5.0.0+incompatible
|
||||||
|
@ -342,6 +342,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
|||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/csrf v1.6.2 h1:QqQ/OWwuFp4jMKgBFAzJVW3FMULdyUW7JoM4pEWuqKg=
|
||||||
|
github.com/gorilla/csrf v1.6.2/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||||
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI=
|
github.com/gorilla/handlers v1.3.0 h1:tsg9qP3mjt1h4Roxp+M1paRjrVBfPSOpBuVclh6YluI=
|
||||||
github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
github.com/gorilla/handlers v1.3.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
@ -349,6 +351,8 @@ github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
|
|||||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
|
github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
|
||||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
github.com/gorilla/mux v1.7.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/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||||
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo=
|
||||||
|
89
src/server/middleware/csrf/csrf.go
Normal file
89
src/server/middleware/csrf/csrf.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/goharbor/harbor/src/common/utils"
|
||||||
|
"github.com/goharbor/harbor/src/common/utils/log"
|
||||||
|
"github.com/goharbor/harbor/src/core/config"
|
||||||
|
ierror "github.com/goharbor/harbor/src/internal/error"
|
||||||
|
serror "github.com/goharbor/harbor/src/server/error"
|
||||||
|
"github.com/goharbor/harbor/src/server/middleware"
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
csrfKeyEnv = "CSRF_KEY"
|
||||||
|
tokenHeader = "X-Harbor-CSRF-Token"
|
||||||
|
tokenCookie = "__csrf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
once sync.Once
|
||||||
|
protect func(handler http.Handler) http.Handler
|
||||||
|
)
|
||||||
|
|
||||||
|
// attachToken makes sure if csrf generate a new token it will be included in the response header
|
||||||
|
func attachToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if t := csrf.Token(r); len(t) > 0 {
|
||||||
|
http.SetCookie(w, &http.Cookie{
|
||||||
|
Name: tokenCookie,
|
||||||
|
Secure: true,
|
||||||
|
Value: t,
|
||||||
|
Path: "/",
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
log.Warningf("token not found in context, skip attaching")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(w http.ResponseWriter, r *http.Request) {
|
||||||
|
attachToken(w, r)
|
||||||
|
serror.SendError(w, ierror.New(csrf.FailureReason(r)).WithCode(ierror.ForbiddenCode))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func attach(handler http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
attachToken(rw, req)
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Middleware initialize the middleware to apply csrf selectively
|
||||||
|
func Middleware() func(handler http.Handler) http.Handler {
|
||||||
|
once.Do(func() {
|
||||||
|
key := os.Getenv(csrfKeyEnv)
|
||||||
|
if len(key) != 32 {
|
||||||
|
log.Warningf("Invalid CSRF key from environment: %s, generating random key...", key)
|
||||||
|
key = utils.GenerateRandomString()
|
||||||
|
|
||||||
|
}
|
||||||
|
protect = csrf.Protect([]byte(key), csrf.RequestHeader(tokenHeader),
|
||||||
|
csrf.ErrorHandler(http.HandlerFunc(handleError)),
|
||||||
|
csrf.SameSite(csrf.SameSiteStrictMode),
|
||||||
|
csrf.Path("/"))
|
||||||
|
})
|
||||||
|
return middleware.New(func(rw http.ResponseWriter, req *http.Request, next http.Handler) {
|
||||||
|
protect(attach(next)).ServeHTTP(rw, req)
|
||||||
|
}, csrfSkipper)
|
||||||
|
}
|
||||||
|
|
||||||
|
// csrfSkipper makes sure only some of the uris accessed by non-UI client can skip the csrf check
|
||||||
|
func csrfSkipper(req *http.Request) bool {
|
||||||
|
path := req.URL.Path
|
||||||
|
// We can check the cookie directly b/c the filter and controllerRegistry is executed after middleware, so no session
|
||||||
|
// cookie is added by beego.
|
||||||
|
_, err := req.Cookie(config.SessionCookieName)
|
||||||
|
hasSession := err == nil
|
||||||
|
if (strings.HasPrefix(path, "/v2/") ||
|
||||||
|
strings.HasPrefix(path, "/api/") ||
|
||||||
|
strings.HasPrefix(path, "/chartrepo/") ||
|
||||||
|
strings.HasPrefix(path, "/service/")) && !hasSession {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
60
src/server/middleware/csrf/csrf_test.go
Normal file
60
src/server/middleware/csrf/csrf_test.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMiddleware(t *testing.T) {
|
||||||
|
srv := Middleware()(&handler{})
|
||||||
|
cases := []struct {
|
||||||
|
req *http.Request
|
||||||
|
statusCode int
|
||||||
|
returnToken bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
req: httptest.NewRequest(http.MethodGet, "/", nil),
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
returnToken: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
req: httptest.NewRequest(http.MethodDelete, "/", nil),
|
||||||
|
statusCode: http.StatusForbidden,
|
||||||
|
returnToken: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
req: httptest.NewRequest(http.MethodGet, "/api/2.0/projects", nil), // should be skipped
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
returnToken: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
req: httptest.NewRequest(http.MethodDelete, "/v2/library/hello-world/manifests/latest", nil), // should be skipped
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
returnToken: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
srv.ServeHTTP(rec, c.req)
|
||||||
|
assert.Equal(t, c.statusCode, rec.Result().StatusCode)
|
||||||
|
assert.Equal(t, c.returnToken, hasCookie(rec.Result(), tokenCookie))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasCookie(resp *http.Response, name string) bool {
|
||||||
|
for _, c := range resp.Cookies() {
|
||||||
|
if c != nil && c.Name == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
20
src/vendor/github.com/gorilla/csrf/AUTHORS
generated
vendored
Normal file
20
src/vendor/github.com/gorilla/csrf/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# This is the official list of gorilla/csrf authors for copyright purposes.
|
||||||
|
# Please keep the list sorted.
|
||||||
|
|
||||||
|
adiabatic <adiabatic@users.noreply.github.com>
|
||||||
|
Google LLC (https://opensource.google.com)
|
||||||
|
jamesgroat <james@groat.com>
|
||||||
|
Joshua Carp <jm.carp@gmail.com>
|
||||||
|
Kamil Kisiel <kamil@kamilkisiel.net>
|
||||||
|
Kevin Burke <kev@inburke.com>
|
||||||
|
Kévin Dunglas <dunglas@gmail.com>
|
||||||
|
Kristoffer Berdal <web@flexd.net>
|
||||||
|
Martin Angers <martin.n.angers@gmail.com>
|
||||||
|
Matt Silverlock <matt@eatsleeprepeat.net>
|
||||||
|
Philip I. Thomas <mail@philipithomas.com>
|
||||||
|
Richard Musiol <mail@richard-musiol.de>
|
||||||
|
Seth Hoenig <seth.a.hoenig@gmail.com>
|
||||||
|
Stefano Vettorazzi <stefanovc@gmail.com>
|
||||||
|
Wayne Ashley Berry <wayneashleyberry@gmail.com>
|
||||||
|
田浩浩 <llitfkitfk@gmail.com>
|
||||||
|
陈东海 <cdh_cjx@163.com>
|
27
src/vendor/github.com/gorilla/csrf/Gopkg.lock
generated
vendored
Normal file
27
src/vendor/github.com/gorilla/csrf/Gopkg.lock
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/gorilla/context"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a"
|
||||||
|
version = "v1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/gorilla/securecookie"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "667fe4e3466a040b780561fe9b51a83a3753eefc"
|
||||||
|
version = "v1.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||||
|
version = "v0.8.0"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "1695686bc8fa0eb76df9fe8c5ca473686071ddcf795a0595a9465a03e8ac9bef"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
34
src/vendor/github.com/gorilla/csrf/Gopkg.toml
generated
vendored
Normal file
34
src/vendor/github.com/gorilla/csrf/Gopkg.toml
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/gorilla/context"
|
||||||
|
version = "1.1.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/gorilla/securecookie"
|
||||||
|
version = "1.1.0"
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/pkg/errors"
|
||||||
|
version = "0.8.0"
|
26
src/vendor/github.com/gorilla/csrf/LICENSE
generated
vendored
Normal file
26
src/vendor/github.com/gorilla/csrf/LICENSE
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
Copyright (c) 2015-2018, The Gorilla Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
352
src/vendor/github.com/gorilla/csrf/README.md
generated
vendored
Normal file
352
src/vendor/github.com/gorilla/csrf/README.md
generated
vendored
Normal file
@ -0,0 +1,352 @@
|
|||||||
|
# gorilla/csrf
|
||||||
|
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/csrf?status.svg)](https://godoc.org/github.com/gorilla/csrf)
|
||||||
|
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/csrf/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/csrf?badge)
|
||||||
|
[![Reviewed by Hound](https://img.shields.io/badge/Reviewed_by-Hound-8E64B0.svg)](https://houndci.com)
|
||||||
|
[![CircleCI](https://circleci.com/gh/gorilla/csrf.svg?style=svg)](https://circleci.com/gh/gorilla/csrf)
|
||||||
|
|
||||||
|
gorilla/csrf is a HTTP middleware library that provides [cross-site request
|
||||||
|
forgery](http://blog.codinghorror.com/preventing-csrf-and-xsrf-attacks/) (CSRF)
|
||||||
|
protection. It includes:
|
||||||
|
|
||||||
|
- The `csrf.Protect` middleware/handler provides CSRF protection on routes
|
||||||
|
attached to a router or a sub-router.
|
||||||
|
- A `csrf.Token` function that provides the token to pass into your response,
|
||||||
|
whether that be a HTML form or a JSON response body.
|
||||||
|
- ... and a `csrf.TemplateField` helper that you can pass into your `html/template`
|
||||||
|
templates to replace a `{{ .csrfField }}` template tag with a hidden input
|
||||||
|
field.
|
||||||
|
|
||||||
|
gorilla/csrf is designed to work with any Go web framework, including:
|
||||||
|
|
||||||
|
- The [Gorilla](https://www.gorillatoolkit.org/) toolkit
|
||||||
|
- Go's built-in [net/http](http://golang.org/pkg/net/http/) package
|
||||||
|
- [Goji](https://goji.io) - see the [tailored fork](https://github.com/goji/csrf)
|
||||||
|
- [Gin](https://github.com/gin-gonic/gin)
|
||||||
|
- [Echo](https://github.com/labstack/echo)
|
||||||
|
- ... and any other router/framework that rallies around Go's `http.Handler` interface.
|
||||||
|
|
||||||
|
gorilla/csrf is also compatible with middleware 'helper' libraries like
|
||||||
|
[Alice](https://github.com/justinas/alice) and [Negroni](https://github.com/codegangsta/negroni).
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
With a properly configured Go toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
go get github.com/gorilla/csrf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
- [HTML Forms](#html-forms)
|
||||||
|
- [JavaScript Apps](#javascript-applications)
|
||||||
|
- [Google App Engine](#google-app-engine)
|
||||||
|
- [Setting SameSite](#setting-samesite)
|
||||||
|
- [Setting Options](#setting-options)
|
||||||
|
|
||||||
|
gorilla/csrf is easy to use: add the middleware to your router with
|
||||||
|
the below:
|
||||||
|
|
||||||
|
```go
|
||||||
|
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
|
||||||
|
http.ListenAndServe(":8000", CSRF(r))
|
||||||
|
```
|
||||||
|
|
||||||
|
...and then collect the token with `csrf.Token(r)` in your handlers before
|
||||||
|
passing it to the template, JSON body or HTTP header (see below).
|
||||||
|
|
||||||
|
Note that the authentication key passed to `csrf.Protect([]byte(key))` should be
|
||||||
|
32-bytes long and persist across application restarts. Generating a random key
|
||||||
|
won't allow you to authenticate existing cookies and will break your CSRF
|
||||||
|
validation.
|
||||||
|
|
||||||
|
gorilla/csrf inspects the HTTP headers (first) and form body (second) on
|
||||||
|
subsequent POST/PUT/PATCH/DELETE/etc. requests for the token.
|
||||||
|
|
||||||
|
### HTML Forms
|
||||||
|
|
||||||
|
Here's the common use-case: HTML forms you want to provide CSRF protection for,
|
||||||
|
in order to protect malicious POST requests being made:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/signup", ShowSignupForm)
|
||||||
|
// All POST requests without a valid token will return HTTP 403 Forbidden.
|
||||||
|
// We should also ensure that our mutating (non-idempotent) handler only
|
||||||
|
// matches on POST requests. We can check that here, at the router level, or
|
||||||
|
// within the handler itself via r.Method.
|
||||||
|
r.HandleFunc("/signup/post", SubmitSignupForm).Methods("POST")
|
||||||
|
|
||||||
|
// Add the middleware to your router by wrapping it.
|
||||||
|
http.ListenAndServe(":8000",
|
||||||
|
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
|
||||||
|
// PS: Don't forget to pass csrf.Secure(false) if you're developing locally
|
||||||
|
// over plain HTTP (just don't leave it on in production).
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// signup_form.tmpl just needs a {{ .csrfField }} template tag for
|
||||||
|
// csrf.TemplateField to inject the CSRF token into. Easy!
|
||||||
|
t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
|
||||||
|
csrf.TemplateTag: csrf.TemplateField(r),
|
||||||
|
})
|
||||||
|
// We could also retrieve the token directly from csrf.Token(r) and
|
||||||
|
// set it in the request header - w.Header.Set("X-CSRF-Token", token)
|
||||||
|
// This is useful if you're sending JSON to clients or a front-end JavaScript
|
||||||
|
// framework.
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// We can trust that requests making it this far have satisfied
|
||||||
|
// our CSRF protection requirements.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the CSRF middleware will (by necessity) consume the request body if the
|
||||||
|
token is passed via POST form values. If you need to consume this in your
|
||||||
|
handler, insert your own middleware earlier in the chain to capture the request
|
||||||
|
body.
|
||||||
|
|
||||||
|
### JavaScript Applications
|
||||||
|
|
||||||
|
This approach is useful if you're using a front-end JavaScript framework like
|
||||||
|
React, Ember or Angular, and are providing a JSON API. Specifically, we need
|
||||||
|
to provide a way for our front-end fetch/AJAX calls to pass the token on each
|
||||||
|
fetch (AJAX/XMLHttpRequest) request. We achieve this by:
|
||||||
|
|
||||||
|
- Parsing the token from the `<input>` field generated by the
|
||||||
|
`csrf.TemplateField(r)` helper, or passing it back in a response header.
|
||||||
|
- Sending this token back on every request
|
||||||
|
- Ensuring our cookie is attached to the request so that the form/header
|
||||||
|
value can be compared to the cookie value.
|
||||||
|
|
||||||
|
We'll also look at applying selective CSRF protection using
|
||||||
|
[gorilla/mux's](https://www.gorillatoolkit.org/pkg/mux) sub-routers,
|
||||||
|
as we don't handle any POST/PUT/DELETE requests with our top-level router.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))
|
||||||
|
|
||||||
|
api := r.PathPrefix("/api").Subrouter()
|
||||||
|
api.Use(csrfMiddleware)
|
||||||
|
api.HandleFunc("/user/{id}", GetUser).Methods("GET")
|
||||||
|
|
||||||
|
http.ListenAndServe(":8000", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Authenticate the request, get the id from the route params,
|
||||||
|
// and fetch the user from the DB, etc.
|
||||||
|
|
||||||
|
// Get the token and pass it in the CSRF header. Our JSON-speaking client
|
||||||
|
// or JavaScript framework can now read the header and return the token in
|
||||||
|
// in its own "X-CSRF-Token" request header on the subsequent POST.
|
||||||
|
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
||||||
|
b, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In our JavaScript application, we should read the token from the response
|
||||||
|
headers and pass it in a request header for all requests. Here's what that
|
||||||
|
looks like when using [Axios](https://github.com/axios/axios), a popular
|
||||||
|
JavaScript HTTP client library:
|
||||||
|
|
||||||
|
```js
|
||||||
|
// You can alternatively parse the response header for the X-CSRF-Token, and
|
||||||
|
// store that instead, if you followed the steps above to write the token to a
|
||||||
|
// response header.
|
||||||
|
let csrfToken = document.getElementsByName("gorilla.csrf.Token")[0].value
|
||||||
|
|
||||||
|
// via https://github.com/axios/axios#creating-an-instance
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL: "https://example.com/api/",
|
||||||
|
timeout: 1000,
|
||||||
|
headers: { "X-CSRF-Token": csrfToken }
|
||||||
|
})
|
||||||
|
|
||||||
|
// Now, any HTTP request you make will include the csrfToken from the page,
|
||||||
|
// provided you update the csrfToken variable for each render.
|
||||||
|
try {
|
||||||
|
let resp = await instance.post(endpoint, formData)
|
||||||
|
// Do something with resp
|
||||||
|
} catch (err) {
|
||||||
|
// Handle the exception
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If you plan to host your JavaScript application on another domain, you can use the Trusted Origins
|
||||||
|
feature to allow the host of your JavaScript application to make requests to your Go application. Observe the example below:
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"), csrf.TrustedOrigin([]string{"ui.domain.com"}))
|
||||||
|
|
||||||
|
api := r.PathPrefix("/api").Subrouter()
|
||||||
|
api.Use(csrfMiddleware)
|
||||||
|
api.HandleFunc("/user/{id}", GetUser).Methods("GET")
|
||||||
|
|
||||||
|
http.ListenAndServe(":8000", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Authenticate the request, get the id from the route params,
|
||||||
|
// and fetch the user from the DB, etc.
|
||||||
|
|
||||||
|
// Get the token and pass it in the CSRF header. Our JSON-speaking client
|
||||||
|
// or JavaScript framework can now read the header and return the token in
|
||||||
|
// in its own "X-CSRF-Token" request header on the subsequent POST.
|
||||||
|
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
||||||
|
b, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
On the example above, you're authorizing requests from `ui.domain.com` to make valid CSRF requests to your application, so you can have your API server on another domain without problems.
|
||||||
|
|
||||||
|
### Google App Engine
|
||||||
|
|
||||||
|
If you're using [Google App
|
||||||
|
Engine](https://cloud.google.com/appengine/docs/go/how-requests-are-handled#Go_Requests_and_HTTP),
|
||||||
|
(first-generation) which doesn't allow you to hook into the default `http.ServeMux` directly,
|
||||||
|
you can still use gorilla/csrf (and gorilla/mux):
|
||||||
|
|
||||||
|
```go
|
||||||
|
package app
|
||||||
|
|
||||||
|
// Remember: appengine has its own package main
|
||||||
|
func init() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/", IndexHandler)
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// We pass our CSRF-protected router to the DefaultServeMux
|
||||||
|
http.Handle("/", csrf.Protect([]byte(your-key))(r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: You can ignore this if you're using the
|
||||||
|
[second-generation](https://cloud.google.com/appengine/docs/go/) Go runtime
|
||||||
|
on App Engine (Go 1.11 and above).
|
||||||
|
|
||||||
|
### Setting SameSite
|
||||||
|
|
||||||
|
Go 1.11 introduced the option to set the SameSite attribute in cookies. This is
|
||||||
|
valuable if a developer wants to instruct a browser to not include cookies during
|
||||||
|
a cross site request. SameSiteStrictMode prevents all cross site requests from including
|
||||||
|
the cookie. SameSiteLaxMode prevents CSRF prone requests (POST) from including the cookie
|
||||||
|
but allows the cookie to be included in GET requests to support external linking.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
CSRF := csrf.Protect(
|
||||||
|
[]byte("a-32-byte-long-key-goes-here"),
|
||||||
|
// instruct the browser to never send cookies during cross site requests
|
||||||
|
csrf.SameSite(csrf.SameSiteStrictMode),
|
||||||
|
)
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/signup", GetSignupForm)
|
||||||
|
r.HandleFunc("/signup/post", PostSignupForm)
|
||||||
|
|
||||||
|
http.ListenAndServe(":8000", CSRF(r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setting Options
|
||||||
|
|
||||||
|
What about providing your own error handler and changing the HTTP header the
|
||||||
|
package inspects on requests? (i.e. an existing API you're porting to Go). Well,
|
||||||
|
gorilla/csrf provides options for changing these as you see fit:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func main() {
|
||||||
|
CSRF := csrf.Protect(
|
||||||
|
[]byte("a-32-byte-long-key-goes-here"),
|
||||||
|
csrf.RequestHeader("Authenticity-Token"),
|
||||||
|
csrf.FieldName("authenticity_token"),
|
||||||
|
csrf.ErrorHandler(http.HandlerFunc(serverError(403))),
|
||||||
|
)
|
||||||
|
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/signup", GetSignupForm)
|
||||||
|
r.HandleFunc("/signup/post", PostSignupForm)
|
||||||
|
|
||||||
|
http.ListenAndServe(":8000", CSRF(r))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Not too bad, right?
|
||||||
|
|
||||||
|
If there's something you're confused about or a feature you would like to see
|
||||||
|
added, open an issue.
|
||||||
|
|
||||||
|
## Design Notes
|
||||||
|
|
||||||
|
Getting CSRF protection right is important, so here's some background:
|
||||||
|
|
||||||
|
- This library generates unique-per-request (masked) tokens as a mitigation
|
||||||
|
against the [BREACH attack](http://breachattack.com/).
|
||||||
|
- The 'base' (unmasked) token is stored in the session, which means that
|
||||||
|
multiple browser tabs won't cause a user problems as their per-request token
|
||||||
|
is compared with the base token.
|
||||||
|
- Operates on a "whitelist only" approach where safe (non-mutating) HTTP methods
|
||||||
|
(GET, HEAD, OPTIONS, TRACE) are the _only_ methods where token validation is not
|
||||||
|
enforced.
|
||||||
|
- The design is based on the battle-tested
|
||||||
|
[Django](https://docs.djangoproject.com/en/1.8/ref/csrf/) and [Ruby on
|
||||||
|
Rails](http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html)
|
||||||
|
approaches.
|
||||||
|
- Cookies are authenticated and based on the [securecookie](https://github.com/gorilla/securecookie)
|
||||||
|
library. They're also Secure (issued over HTTPS only) and are HttpOnly
|
||||||
|
by default, because sane defaults are important.
|
||||||
|
- Cookie SameSite attribute (prevents cookies from being sent by a browser
|
||||||
|
during cross site requests) are not set by default to maintain backwards compatibility
|
||||||
|
for legacy systems. The SameSite attribute can be set with the SameSite option.
|
||||||
|
- Go's `crypto/rand` library is used to generate the 32 byte (256 bit) tokens
|
||||||
|
and the one-time-pad used for masking them.
|
||||||
|
|
||||||
|
This library does not seek to be adventurous.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD licensed. See the LICENSE file for details.
|
29
src/vendor/github.com/gorilla/csrf/context.go
generated
vendored
Normal file
29
src/vendor/github.com/gorilla/csrf/context.go
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// +build go1.7
|
||||||
|
|
||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func contextGet(r *http.Request, key string) (interface{}, error) {
|
||||||
|
val := r.Context().Value(key)
|
||||||
|
if val == nil {
|
||||||
|
return nil, errors.Errorf("no value exists in the context for key %q", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextSave(r *http.Request, key string, val interface{}) *http.Request {
|
||||||
|
ctx := r.Context()
|
||||||
|
ctx = context.WithValue(ctx, key, val)
|
||||||
|
return r.WithContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClear(r *http.Request) {
|
||||||
|
// no-op for go1.7+
|
||||||
|
}
|
309
src/vendor/github.com/gorilla/csrf/csrf.go
generated
vendored
Normal file
309
src/vendor/github.com/gorilla/csrf/csrf.go
generated
vendored
Normal file
@ -0,0 +1,309 @@
|
|||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CSRF token length in bytes.
|
||||||
|
const tokenLength = 32
|
||||||
|
|
||||||
|
// Context/session keys & prefixes
|
||||||
|
const (
|
||||||
|
tokenKey string = "gorilla.csrf.Token"
|
||||||
|
formKey string = "gorilla.csrf.Form"
|
||||||
|
errorKey string = "gorilla.csrf.Error"
|
||||||
|
skipCheckKey string = "gorilla.csrf.Skip"
|
||||||
|
cookieName string = "_gorilla_csrf"
|
||||||
|
errorPrefix string = "gorilla/csrf: "
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// The name value used in form fields.
|
||||||
|
fieldName = tokenKey
|
||||||
|
// defaultAge sets the default MaxAge for cookies.
|
||||||
|
defaultAge = 3600 * 12
|
||||||
|
// The default HTTP request header to inspect
|
||||||
|
headerName = "X-CSRF-Token"
|
||||||
|
// Idempotent (safe) methods as defined by RFC7231 section 4.2.2.
|
||||||
|
safeMethods = []string{"GET", "HEAD", "OPTIONS", "TRACE"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TemplateTag provides a default template tag - e.g. {{ .csrfField }} - for use
|
||||||
|
// with the TemplateField function.
|
||||||
|
var TemplateTag = "csrfField"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrNoReferer is returned when a HTTPS request provides an empty Referer
|
||||||
|
// header.
|
||||||
|
ErrNoReferer = errors.New("referer not supplied")
|
||||||
|
// ErrBadReferer is returned when the scheme & host in the URL do not match
|
||||||
|
// the supplied Referer header.
|
||||||
|
ErrBadReferer = errors.New("referer invalid")
|
||||||
|
// ErrNoToken is returned if no CSRF token is supplied in the request.
|
||||||
|
ErrNoToken = errors.New("CSRF token not found in request")
|
||||||
|
// ErrBadToken is returned if the CSRF token in the request does not match
|
||||||
|
// the token in the session, or is otherwise malformed.
|
||||||
|
ErrBadToken = errors.New("CSRF token invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
// SameSiteMode allows a server to define a cookie attribute making it impossible for
|
||||||
|
// the browser to send this cookie along with cross-site requests. The main
|
||||||
|
// goal is to mitigate the risk of cross-origin information leakage, and provide
|
||||||
|
// some protection against cross-site request forgery attacks.
|
||||||
|
//
|
||||||
|
// See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details.
|
||||||
|
type SameSiteMode int
|
||||||
|
|
||||||
|
// SameSite options
|
||||||
|
const (
|
||||||
|
SameSiteDefaultMode SameSiteMode = iota + 1
|
||||||
|
SameSiteLaxMode
|
||||||
|
SameSiteStrictMode
|
||||||
|
SameSiteNoneMode
|
||||||
|
)
|
||||||
|
|
||||||
|
type csrf struct {
|
||||||
|
h http.Handler
|
||||||
|
sc *securecookie.SecureCookie
|
||||||
|
st store
|
||||||
|
opts options
|
||||||
|
}
|
||||||
|
|
||||||
|
// options contains the optional settings for the CSRF middleware.
|
||||||
|
type options struct {
|
||||||
|
MaxAge int
|
||||||
|
Domain string
|
||||||
|
Path string
|
||||||
|
// Note that the function and field names match the case of the associated
|
||||||
|
// http.Cookie field instead of the "correct" HTTPOnly name that golint suggests.
|
||||||
|
HttpOnly bool
|
||||||
|
Secure bool
|
||||||
|
SameSite SameSiteMode
|
||||||
|
RequestHeader string
|
||||||
|
FieldName string
|
||||||
|
ErrorHandler http.Handler
|
||||||
|
CookieName string
|
||||||
|
TrustedOrigins []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect is HTTP middleware that provides Cross-Site Request Forgery
|
||||||
|
// protection.
|
||||||
|
//
|
||||||
|
// It securely generates a masked (unique-per-request) token that
|
||||||
|
// can be embedded in the HTTP response (e.g. form field or HTTP header).
|
||||||
|
// The original (unmasked) token is stored in the session, which is inaccessible
|
||||||
|
// by an attacker (provided you are using HTTPS). Subsequent requests are
|
||||||
|
// expected to include this token, which is compared against the session token.
|
||||||
|
// Requests that do not provide a matching token are served with a HTTP 403
|
||||||
|
// 'Forbidden' error response.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "html/template"
|
||||||
|
//
|
||||||
|
// "github.com/gorilla/csrf"
|
||||||
|
// "github.com/gorilla/mux"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// var t = template.Must(template.New("signup_form.tmpl").Parse(form))
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// r := mux.NewRouter()
|
||||||
|
//
|
||||||
|
// r.HandleFunc("/signup", GetSignupForm)
|
||||||
|
// // POST requests without a valid token will return a HTTP 403 Forbidden.
|
||||||
|
// r.HandleFunc("/signup/post", PostSignupForm)
|
||||||
|
//
|
||||||
|
// // Add the middleware to your router.
|
||||||
|
// http.ListenAndServe(":8000",
|
||||||
|
// // Note that the authentication key provided should be 32 bytes
|
||||||
|
// // long and persist across application restarts.
|
||||||
|
// csrf.Protect([]byte("32-byte-long-auth-key"))(r))
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func GetSignupForm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// // signup_form.tmpl just needs a {{ .csrfField }} template tag for
|
||||||
|
// // csrf.TemplateField to inject the CSRF token into. Easy!
|
||||||
|
// t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
|
||||||
|
// csrf.TemplateTag: csrf.TemplateField(r),
|
||||||
|
// })
|
||||||
|
// // We could also retrieve the token directly from csrf.Token(r) and
|
||||||
|
// // set it in the request header - w.Header.Set("X-CSRF-Token", token)
|
||||||
|
// // This is useful if you're sending JSON to clients or a front-end JavaScript
|
||||||
|
// // framework.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler {
|
||||||
|
return func(h http.Handler) http.Handler {
|
||||||
|
cs := parseOptions(h, opts...)
|
||||||
|
|
||||||
|
// Set the defaults if no options have been specified
|
||||||
|
if cs.opts.ErrorHandler == nil {
|
||||||
|
cs.opts.ErrorHandler = http.HandlerFunc(unauthorizedHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs.opts.MaxAge < 0 {
|
||||||
|
// Default of 12 hours
|
||||||
|
cs.opts.MaxAge = defaultAge
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs.opts.FieldName == "" {
|
||||||
|
cs.opts.FieldName = fieldName
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs.opts.CookieName == "" {
|
||||||
|
cs.opts.CookieName = cookieName
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs.opts.RequestHeader == "" {
|
||||||
|
cs.opts.RequestHeader = headerName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create an authenticated securecookie instance.
|
||||||
|
if cs.sc == nil {
|
||||||
|
cs.sc = securecookie.New(authKey, nil)
|
||||||
|
// Use JSON serialization (faster than one-off gob encoding)
|
||||||
|
cs.sc.SetSerializer(securecookie.JSONEncoder{})
|
||||||
|
// Set the MaxAge of the underlying securecookie.
|
||||||
|
cs.sc.MaxAge(cs.opts.MaxAge)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cs.st == nil {
|
||||||
|
// Default to the cookieStore
|
||||||
|
cs.st = &cookieStore{
|
||||||
|
name: cs.opts.CookieName,
|
||||||
|
maxAge: cs.opts.MaxAge,
|
||||||
|
secure: cs.opts.Secure,
|
||||||
|
httpOnly: cs.opts.HttpOnly,
|
||||||
|
sameSite: cs.opts.SameSite,
|
||||||
|
path: cs.opts.Path,
|
||||||
|
domain: cs.opts.Domain,
|
||||||
|
sc: cs.sc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements http.Handler for the csrf type.
|
||||||
|
func (cs *csrf) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Skip the check if directed to. This should always be a bool.
|
||||||
|
if val, err := contextGet(r, skipCheckKey); err == nil {
|
||||||
|
if skip, ok := val.(bool); ok {
|
||||||
|
if skip {
|
||||||
|
cs.h.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the token from the session.
|
||||||
|
// An error represents either a cookie that failed HMAC validation
|
||||||
|
// or that doesn't exist.
|
||||||
|
realToken, err := cs.st.Get(r)
|
||||||
|
if err != nil || len(realToken) != tokenLength {
|
||||||
|
// If there was an error retrieving the token, the token doesn't exist
|
||||||
|
// yet, or it's the wrong length, generate a new token.
|
||||||
|
// Note that the new token will (correctly) fail validation downstream
|
||||||
|
// as it will no longer match the request token.
|
||||||
|
realToken, err = generateRandomBytes(tokenLength)
|
||||||
|
if err != nil {
|
||||||
|
r = envError(r, err)
|
||||||
|
cs.opts.ErrorHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the new (real) token in the session store.
|
||||||
|
err = cs.st.Save(realToken, w)
|
||||||
|
if err != nil {
|
||||||
|
r = envError(r, err)
|
||||||
|
cs.opts.ErrorHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the masked token to the request context
|
||||||
|
r = contextSave(r, tokenKey, mask(realToken, r))
|
||||||
|
// Save the field name to the request context
|
||||||
|
r = contextSave(r, formKey, cs.opts.FieldName)
|
||||||
|
|
||||||
|
// HTTP methods not defined as idempotent ("safe") under RFC7231 require
|
||||||
|
// inspection.
|
||||||
|
if !contains(safeMethods, r.Method) {
|
||||||
|
// Enforce an origin check for HTTPS connections. As per the Django CSRF
|
||||||
|
// implementation (https://goo.gl/vKA7GE) the Referer header is almost
|
||||||
|
// always present for same-domain HTTP requests.
|
||||||
|
if r.URL.Scheme == "https" {
|
||||||
|
// Fetch the Referer value. Call the error handler if it's empty or
|
||||||
|
// otherwise fails to parse.
|
||||||
|
referer, err := url.Parse(r.Referer())
|
||||||
|
if err != nil || referer.String() == "" {
|
||||||
|
r = envError(r, ErrNoReferer)
|
||||||
|
cs.opts.ErrorHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
valid := sameOrigin(r.URL, referer)
|
||||||
|
|
||||||
|
if !valid {
|
||||||
|
for _, trustedOrigin := range cs.opts.TrustedOrigins {
|
||||||
|
if referer.Host == trustedOrigin {
|
||||||
|
valid = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid == false {
|
||||||
|
r = envError(r, ErrBadReferer)
|
||||||
|
cs.opts.ErrorHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the token returned from the session store is nil for non-idempotent
|
||||||
|
// ("unsafe") methods, call the error handler.
|
||||||
|
if realToken == nil {
|
||||||
|
r = envError(r, ErrNoToken)
|
||||||
|
cs.opts.ErrorHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the combined token (pad + masked) token and unmask it.
|
||||||
|
requestToken := unmask(cs.requestToken(r))
|
||||||
|
|
||||||
|
// Compare the request token against the real token
|
||||||
|
if !compareTokens(requestToken, realToken) {
|
||||||
|
r = envError(r, ErrBadToken)
|
||||||
|
cs.opts.ErrorHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Vary: Cookie header to protect clients from caching the response.
|
||||||
|
w.Header().Add("Vary", "Cookie")
|
||||||
|
|
||||||
|
// Call the wrapped handler/router on success.
|
||||||
|
cs.h.ServeHTTP(w, r)
|
||||||
|
// Clear the request context after the handler has completed.
|
||||||
|
contextClear(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unauthorizedhandler sets a HTTP 403 Forbidden status and writes the
|
||||||
|
// CSRF failure reason to the response.
|
||||||
|
func unauthorizedHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, fmt.Sprintf("%s - %s",
|
||||||
|
http.StatusText(http.StatusForbidden), FailureReason(r)),
|
||||||
|
http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
176
src/vendor/github.com/gorilla/csrf/doc.go
generated
vendored
Normal file
176
src/vendor/github.com/gorilla/csrf/doc.go
generated
vendored
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
Package csrf (gorilla/csrf) provides Cross Site Request Forgery (CSRF)
|
||||||
|
prevention middleware for Go web applications & services.
|
||||||
|
|
||||||
|
It includes:
|
||||||
|
|
||||||
|
* The `csrf.Protect` middleware/handler provides CSRF protection on routes
|
||||||
|
attached to a router or a sub-router.
|
||||||
|
|
||||||
|
* A `csrf.Token` function that provides the token to pass into your response,
|
||||||
|
whether that be a HTML form or a JSON response body.
|
||||||
|
|
||||||
|
* ... and a `csrf.TemplateField` helper that you can pass into your `html/template`
|
||||||
|
templates to replace a `{{ .csrfField }}` template tag with a hidden input
|
||||||
|
field.
|
||||||
|
|
||||||
|
gorilla/csrf is easy to use: add the middleware to individual handlers with
|
||||||
|
the below:
|
||||||
|
|
||||||
|
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
|
||||||
|
http.HandlerFunc("/route", CSRF(YourHandler))
|
||||||
|
|
||||||
|
... and then collect the token with `csrf.Token(r)` before passing it to the
|
||||||
|
template, JSON body or HTTP header (you pick!). gorilla/csrf inspects the form body
|
||||||
|
(first) and HTTP headers (second) on subsequent POST/PUT/PATCH/DELETE/etc. requests
|
||||||
|
for the token.
|
||||||
|
|
||||||
|
Note that the authentication key passed to `csrf.Protect([]byte(key))` should be
|
||||||
|
32-bytes long and persist across application restarts. Generating a random key
|
||||||
|
won't allow you to authenticate existing cookies and will break your CSRF
|
||||||
|
validation.
|
||||||
|
|
||||||
|
Here's the common use-case: HTML forms you want to provide CSRF protection for,
|
||||||
|
in order to protect malicious POST requests being made:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
var form = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Sign Up!</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form method="POST" action="/signup/post" accept-charset="UTF-8">
|
||||||
|
<input type="text" name="name">
|
||||||
|
<input type="text" name="email">
|
||||||
|
<!--
|
||||||
|
The default template tag used by the CSRF middleware .
|
||||||
|
This will be replaced with a hidden <input> field containing the
|
||||||
|
masked CSRF token.
|
||||||
|
-->
|
||||||
|
{{ .csrfField }}
|
||||||
|
<input type="submit" value="Sign up!">
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
var t = template.Must(template.New("signup_form.tmpl").Parse(form))
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
r.HandleFunc("/signup", ShowSignupForm)
|
||||||
|
// All POST requests without a valid token will return HTTP 403 Forbidden.
|
||||||
|
// We should also ensure that our mutating (non-idempotent) handler only
|
||||||
|
// matches on POST requests. We can check that here, at the router level, or
|
||||||
|
// within the handler itself via r.Method.
|
||||||
|
r.HandleFunc("/signup/post", SubmitSignupForm).Methods("POST")
|
||||||
|
|
||||||
|
// Add the middleware to your router by wrapping it.
|
||||||
|
http.ListenAndServe(":8000",
|
||||||
|
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
|
||||||
|
// PS: Don't forget to pass csrf.Secure(false) if you're developing locally
|
||||||
|
// over plain HTTP (just don't leave it on in production).
|
||||||
|
}
|
||||||
|
|
||||||
|
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// signup_form.tmpl just needs a {{ .csrfField }} template tag for
|
||||||
|
// csrf.TemplateField to inject the CSRF token into. Easy!
|
||||||
|
t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{}{
|
||||||
|
csrf.TemplateTag: csrf.TemplateField(r),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// We can trust that requests making it this far have satisfied
|
||||||
|
// our CSRF protection requirements.
|
||||||
|
fmt.Fprintf(w, "%v\n", r.PostForm)
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that the CSRF middleware will (by necessity) consume the request body if the
|
||||||
|
token is passed via POST form values. If you need to consume this in your
|
||||||
|
handler, insert your own middleware earlier in the chain to capture the request
|
||||||
|
body.
|
||||||
|
|
||||||
|
You can also send the CSRF token in the response header. This approach is useful
|
||||||
|
if you're using a front-end JavaScript framework like Ember or Angular, or are
|
||||||
|
providing a JSON API:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gorilla/csrf"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
r := mux.NewRouter()
|
||||||
|
|
||||||
|
api := r.PathPrefix("/api").Subrouter()
|
||||||
|
api.HandleFunc("/user/:id", GetUser).Methods("GET")
|
||||||
|
|
||||||
|
http.ListenAndServe(":8000",
|
||||||
|
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUser(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Authenticate the request, get the id from the route params,
|
||||||
|
// and fetch the user from the DB, etc.
|
||||||
|
|
||||||
|
// Get the token and pass it in the CSRF header. Our JSON-speaking client
|
||||||
|
// or JavaScript framework can now read the header and return the token in
|
||||||
|
// in its own "X-CSRF-Token" request header on the subsequent POST.
|
||||||
|
w.Header().Set("X-CSRF-Token", csrf.Token(r))
|
||||||
|
b, err := json.Marshal(user)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
If you're writing a client that's supposed to mimic browser behavior, make sure to
|
||||||
|
send back the CSRF cookie (the default name is _gorilla_csrf, but this can be changed
|
||||||
|
with the CookieName Option) along with either the X-CSRF-Token header or the gorilla.csrf.Token form field.
|
||||||
|
|
||||||
|
In addition: getting CSRF protection right is important, so here's some background:
|
||||||
|
|
||||||
|
* This library generates unique-per-request (masked) tokens as a mitigation
|
||||||
|
against the BREACH attack (http://breachattack.com/).
|
||||||
|
|
||||||
|
* The 'base' (unmasked) token is stored in the session, which means that
|
||||||
|
multiple browser tabs won't cause a user problems as their per-request token
|
||||||
|
is compared with the base token.
|
||||||
|
|
||||||
|
* Operates on a "whitelist only" approach where safe (non-mutating) HTTP methods
|
||||||
|
(GET, HEAD, OPTIONS, TRACE) are the *only* methods where token validation is not
|
||||||
|
enforced.
|
||||||
|
|
||||||
|
* The design is based on the battle-tested Django
|
||||||
|
(https://docs.djangoproject.com/en/1.8/ref/csrf/) and Ruby on Rails
|
||||||
|
(http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection.html)
|
||||||
|
approaches.
|
||||||
|
|
||||||
|
* Cookies are authenticated and based on the securecookie
|
||||||
|
(https://github.com/gorilla/securecookie) library. They're also Secure (issued
|
||||||
|
over HTTPS only) and are HttpOnly by default, because sane defaults are
|
||||||
|
important.
|
||||||
|
|
||||||
|
* Go's `crypto/rand` library is used to generate the 32 byte (256 bit) tokens
|
||||||
|
and the one-time-pad used for masking them.
|
||||||
|
|
||||||
|
This library does not seek to be adventurous.
|
||||||
|
|
||||||
|
*/
|
||||||
|
package csrf
|
6
src/vendor/github.com/gorilla/csrf/go.mod
generated
vendored
Normal file
6
src/vendor/github.com/gorilla/csrf/go.mod
generated
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module github.com/gorilla/csrf
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gorilla/securecookie v1.1.1
|
||||||
|
github.com/pkg/errors v0.8.0
|
||||||
|
)
|
4
src/vendor/github.com/gorilla/csrf/go.sum
generated
vendored
Normal file
4
src/vendor/github.com/gorilla/csrf/go.sum
generated
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
203
src/vendor/github.com/gorilla/csrf/helpers.go
generated
vendored
Normal file
203
src/vendor/github.com/gorilla/csrf/helpers.go
generated
vendored
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token returns a masked CSRF token ready for passing into HTML template or
|
||||||
|
// a JSON response body. An empty token will be returned if the middleware
|
||||||
|
// has not been applied (which will fail subsequent validation).
|
||||||
|
func Token(r *http.Request) string {
|
||||||
|
if val, err := contextGet(r, tokenKey); err == nil {
|
||||||
|
if maskedToken, ok := val.(string); ok {
|
||||||
|
return maskedToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// FailureReason makes CSRF validation errors available in the request context.
|
||||||
|
// This is useful when you want to log the cause of the error or report it to
|
||||||
|
// client.
|
||||||
|
func FailureReason(r *http.Request) error {
|
||||||
|
if val, err := contextGet(r, errorKey); err == nil {
|
||||||
|
if err, ok := val.(error); ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnsafeSkipCheck will skip the CSRF check for any requests. This must be
|
||||||
|
// called before the CSRF middleware.
|
||||||
|
//
|
||||||
|
// Note: You should not set this without otherwise securing the request from
|
||||||
|
// CSRF attacks. The primary use-case for this function is to turn off CSRF
|
||||||
|
// checks for non-browser clients using authorization tokens against your API.
|
||||||
|
func UnsafeSkipCheck(r *http.Request) *http.Request {
|
||||||
|
return contextSave(r, skipCheckKey, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TemplateField is a template helper for html/template that provides an <input> field
|
||||||
|
// populated with a CSRF token.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// // The following tag in our form.tmpl template:
|
||||||
|
// {{ .csrfField }}
|
||||||
|
//
|
||||||
|
// // ... becomes:
|
||||||
|
// <input type="hidden" name="gorilla.csrf.Token" value="<token>">
|
||||||
|
//
|
||||||
|
func TemplateField(r *http.Request) template.HTML {
|
||||||
|
if name, err := contextGet(r, formKey); err == nil {
|
||||||
|
fragment := fmt.Sprintf(`<input type="hidden" name="%s" value="%s">`,
|
||||||
|
name, Token(r))
|
||||||
|
|
||||||
|
return template.HTML(fragment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return template.HTML("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// mask returns a unique-per-request token to mitigate the BREACH attack
|
||||||
|
// as per http://breachattack.com/#mitigations
|
||||||
|
//
|
||||||
|
// The token is generated by XOR'ing a one-time-pad and the base (session) CSRF
|
||||||
|
// token and returning them together as a 64-byte slice. This effectively
|
||||||
|
// randomises the token on a per-request basis without breaking multiple browser
|
||||||
|
// tabs/windows.
|
||||||
|
func mask(realToken []byte, r *http.Request) string {
|
||||||
|
otp, err := generateRandomBytes(tokenLength)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR the OTP with the real token to generate a masked token. Append the
|
||||||
|
// OTP to the front of the masked token to allow unmasking in the subsequent
|
||||||
|
// request.
|
||||||
|
return base64.StdEncoding.EncodeToString(append(otp, xorToken(otp, realToken)...))
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmask splits the issued token (one-time-pad + masked token) and returns the
|
||||||
|
// unmasked request token for comparison.
|
||||||
|
func unmask(issued []byte) []byte {
|
||||||
|
// Issued tokens are always masked and combined with the pad.
|
||||||
|
if len(issued) != tokenLength*2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now know the length of the byte slice.
|
||||||
|
otp := issued[tokenLength:]
|
||||||
|
masked := issued[:tokenLength]
|
||||||
|
|
||||||
|
// Unmask the token by XOR'ing it against the OTP used to mask it.
|
||||||
|
return xorToken(otp, masked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// requestToken returns the issued token (pad + masked token) from the HTTP POST
|
||||||
|
// body or HTTP header. It will return nil if the token fails to decode.
|
||||||
|
func (cs *csrf) requestToken(r *http.Request) []byte {
|
||||||
|
// 1. Check the HTTP header first.
|
||||||
|
issued := r.Header.Get(cs.opts.RequestHeader)
|
||||||
|
|
||||||
|
// 2. Fall back to the POST (form) value.
|
||||||
|
if issued == "" {
|
||||||
|
issued = r.PostFormValue(cs.opts.FieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Finally, fall back to the multipart form (if set).
|
||||||
|
if issued == "" && r.MultipartForm != nil {
|
||||||
|
vals := r.MultipartForm.Value[cs.opts.FieldName]
|
||||||
|
|
||||||
|
if len(vals) > 0 {
|
||||||
|
issued = vals[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the "issued" (pad + masked) token sent in the request. Return a
|
||||||
|
// nil byte slice on a decoding error (this will fail upstream).
|
||||||
|
decoded, err := base64.StdEncoding.DecodeString(issued)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// generateRandomBytes returns securely generated random bytes.
|
||||||
|
// It will return an error if the system's secure random number generator
|
||||||
|
// fails to function correctly.
|
||||||
|
func generateRandomBytes(n int) ([]byte, error) {
|
||||||
|
b := make([]byte, n)
|
||||||
|
_, err := rand.Read(b)
|
||||||
|
// err == nil only if len(b) == n
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// sameOrigin returns true if URLs a and b share the same origin. The same
|
||||||
|
// origin is defined as host (which includes the port) and scheme.
|
||||||
|
func sameOrigin(a, b *url.URL) bool {
|
||||||
|
return (a.Scheme == b.Scheme && a.Host == b.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare securely (constant-time) compares the unmasked token from the request
|
||||||
|
// against the real token from the session.
|
||||||
|
func compareTokens(a, b []byte) bool {
|
||||||
|
// This is required as subtle.ConstantTimeCompare does not check for equal
|
||||||
|
// lengths in Go versions prior to 1.3.
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return subtle.ConstantTimeCompare(a, b) == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// xorToken XORs tokens ([]byte) to provide unique-per-request CSRF tokens. It
|
||||||
|
// will return a masked token if the base token is XOR'ed with a one-time-pad.
|
||||||
|
// An unmasked token will be returned if a masked token is XOR'ed with the
|
||||||
|
// one-time-pad used to mask it.
|
||||||
|
func xorToken(a, b []byte) []byte {
|
||||||
|
n := len(a)
|
||||||
|
if len(b) < n {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
res := make([]byte, n)
|
||||||
|
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
res[i] = a[i] ^ b[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// contains is a helper function to check if a string exists in a slice - e.g.
|
||||||
|
// whether a HTTP method exists in a list of safe methods.
|
||||||
|
func contains(vals []string, s string) bool {
|
||||||
|
for _, v := range vals {
|
||||||
|
if v == s {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// envError stores a CSRF error in the request context.
|
||||||
|
func envError(r *http.Request, err error) *http.Request {
|
||||||
|
return contextSave(r, errorKey, err)
|
||||||
|
}
|
170
src/vendor/github.com/gorilla/csrf/options.go
generated
vendored
Normal file
170
src/vendor/github.com/gorilla/csrf/options.go
generated
vendored
Normal file
@ -0,0 +1,170 @@
|
|||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Option describes a functional option for configuring the CSRF handler.
|
||||||
|
type Option func(*csrf)
|
||||||
|
|
||||||
|
// MaxAge sets the maximum age (in seconds) of a CSRF token's underlying cookie.
|
||||||
|
// Defaults to 12 hours. Call csrf.MaxAge(0) to explicitly set session-only
|
||||||
|
// cookies.
|
||||||
|
func MaxAge(age int) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.MaxAge = age
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Domain sets the cookie domain. Defaults to the current domain of the request
|
||||||
|
// only (recommended).
|
||||||
|
//
|
||||||
|
// This should be a hostname and not a URL. If set, the domain is treated as
|
||||||
|
// being prefixed with a '.' - e.g. "example.com" becomes ".example.com" and
|
||||||
|
// matches "www.example.com" and "secure.example.com".
|
||||||
|
func Domain(domain string) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.Domain = domain
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path sets the cookie path. Defaults to the path the cookie was issued from
|
||||||
|
// (recommended).
|
||||||
|
//
|
||||||
|
// This instructs clients to only respond with cookie for that path and its
|
||||||
|
// subpaths - i.e. a cookie issued from "/register" would be included in requests
|
||||||
|
// to "/register/step2" and "/register/submit".
|
||||||
|
func Path(p string) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.Path = p
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secure sets the 'Secure' flag on the cookie. Defaults to true (recommended).
|
||||||
|
// Set this to 'false' in your development environment otherwise the cookie won't
|
||||||
|
// be sent over an insecure channel. Setting this via the presence of a 'DEV'
|
||||||
|
// environmental variable is a good way of making sure this won't make it to a
|
||||||
|
// production environment.
|
||||||
|
func Secure(s bool) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.Secure = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpOnly sets the 'HttpOnly' flag on the cookie. Defaults to true (recommended).
|
||||||
|
func HttpOnly(h bool) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
// Note that the function and field names match the case of the
|
||||||
|
// related http.Cookie field instead of the "correct" HTTPOnly name
|
||||||
|
// that golint suggests.
|
||||||
|
cs.opts.HttpOnly = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SameSite sets the cookie SameSite attribute. Defaults to blank to maintain
|
||||||
|
// backwards compatibility, however, Strict is recommended.
|
||||||
|
//
|
||||||
|
// SameSite(SameSiteStrictMode) will prevent the cookie from being sent by the
|
||||||
|
// browser to the target site in all cross-site browsing context, even when
|
||||||
|
// following a regular link (GET request).
|
||||||
|
//
|
||||||
|
// SameSite(SameSiteLaxMode) provides a reasonable balance between security and
|
||||||
|
// usability for websites that want to maintain user's logged-in session after
|
||||||
|
// the user arrives from an external link. The session cookie would be allowed
|
||||||
|
// when following a regular link from an external website while blocking it in
|
||||||
|
// CSRF-prone request methods (e.g. POST).
|
||||||
|
//
|
||||||
|
// This option is only available for go 1.11+.
|
||||||
|
func SameSite(s SameSiteMode) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.SameSite = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandler allows you to change the handler called when CSRF request
|
||||||
|
// processing encounters an invalid token or request. A typical use would be to
|
||||||
|
// provide a handler that returns a static HTML file with a HTTP 403 status. By
|
||||||
|
// default a HTTP 403 status and a plain text CSRF failure reason are served.
|
||||||
|
//
|
||||||
|
// Note that a custom error handler can also access the csrf.FailureReason(r)
|
||||||
|
// function to retrieve the CSRF validation reason from the request context.
|
||||||
|
func ErrorHandler(h http.Handler) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.ErrorHandler = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestHeader allows you to change the request header the CSRF middleware
|
||||||
|
// inspects. The default is X-CSRF-Token.
|
||||||
|
func RequestHeader(header string) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.RequestHeader = header
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldName allows you to change the name attribute of the hidden <input> field
|
||||||
|
// inspected by this package. The default is 'gorilla.csrf.Token'.
|
||||||
|
func FieldName(name string) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.FieldName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieName changes the name of the CSRF cookie issued to clients.
|
||||||
|
//
|
||||||
|
// Note that cookie names should not contain whitespace, commas, semicolons,
|
||||||
|
// backslashes or control characters as per RFC6265.
|
||||||
|
func CookieName(name string) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.CookieName = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrustedOrigins configures a set of origins (Referers) that are considered as trusted.
|
||||||
|
// This will allow cross-domain CSRF use-cases - e.g. where the front-end is served
|
||||||
|
// from a different domain than the API server - to correctly pass a CSRF check.
|
||||||
|
//
|
||||||
|
// You should only provide origins you own or have full control over.
|
||||||
|
func TrustedOrigins(origins []string) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.opts.TrustedOrigins = origins
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setStore sets the store used by the CSRF middleware.
|
||||||
|
// Note: this is private (for now) to allow for internal API changes.
|
||||||
|
func setStore(s store) Option {
|
||||||
|
return func(cs *csrf) {
|
||||||
|
cs.st = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseOptions parses the supplied options functions and returns a configured
|
||||||
|
// csrf handler.
|
||||||
|
func parseOptions(h http.Handler, opts ...Option) *csrf {
|
||||||
|
// Set the handler to call after processing.
|
||||||
|
cs := &csrf{
|
||||||
|
h: h,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to true. See Secure & HttpOnly function comments for rationale.
|
||||||
|
// Set here to allow package users to override the default.
|
||||||
|
cs.opts.Secure = true
|
||||||
|
cs.opts.HttpOnly = true
|
||||||
|
|
||||||
|
// Default to blank to maintain backwards compatibility
|
||||||
|
cs.opts.SameSite = SameSiteDefaultMode
|
||||||
|
|
||||||
|
// Default; only override this if the package user explicitly calls MaxAge(0)
|
||||||
|
cs.opts.MaxAge = defaultAge
|
||||||
|
|
||||||
|
// Range over each options function and apply it
|
||||||
|
// to our csrf type to configure it. Options functions are
|
||||||
|
// applied in order, with any conflicting options overriding
|
||||||
|
// earlier calls.
|
||||||
|
for _, option := range opts {
|
||||||
|
option(cs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cs
|
||||||
|
}
|
86
src/vendor/github.com/gorilla/csrf/store.go
generated
vendored
Normal file
86
src/vendor/github.com/gorilla/csrf/store.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// +build go1.11
|
||||||
|
|
||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// store represents the session storage used for CSRF tokens.
|
||||||
|
type store interface {
|
||||||
|
// Get returns the real CSRF token from the store.
|
||||||
|
Get(*http.Request) ([]byte, error)
|
||||||
|
// Save stores the real CSRF token in the store and writes a
|
||||||
|
// cookie to the http.ResponseWriter.
|
||||||
|
// For non-cookie stores, the cookie should contain a unique (256 bit) ID
|
||||||
|
// or key that references the token in the backend store.
|
||||||
|
// csrf.GenerateRandomBytes is a helper function for generating secure IDs.
|
||||||
|
Save(token []byte, w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookieStore is a signed cookie session store for CSRF tokens.
|
||||||
|
type cookieStore struct {
|
||||||
|
name string
|
||||||
|
maxAge int
|
||||||
|
secure bool
|
||||||
|
httpOnly bool
|
||||||
|
path string
|
||||||
|
domain string
|
||||||
|
sc *securecookie.SecureCookie
|
||||||
|
sameSite SameSiteMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a CSRF token from the session cookie. It returns an empty token
|
||||||
|
// if decoding fails (e.g. HMAC validation fails or the named cookie doesn't exist).
|
||||||
|
func (cs *cookieStore) Get(r *http.Request) ([]byte, error) {
|
||||||
|
// Retrieve the cookie from the request
|
||||||
|
cookie, err := r.Cookie(cs.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := make([]byte, tokenLength)
|
||||||
|
// Decode the HMAC authenticated cookie.
|
||||||
|
err = cs.sc.Decode(cs.name, cookie.Value, &token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save stores the CSRF token in the session cookie.
|
||||||
|
func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
|
||||||
|
// Generate an encoded cookie value with the CSRF token.
|
||||||
|
encoded, err := cs.sc.Encode(cs.name, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: cs.name,
|
||||||
|
Value: encoded,
|
||||||
|
MaxAge: cs.maxAge,
|
||||||
|
HttpOnly: cs.httpOnly,
|
||||||
|
Secure: cs.secure,
|
||||||
|
SameSite: http.SameSite(cs.sameSite),
|
||||||
|
Path: cs.path,
|
||||||
|
Domain: cs.domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Expires field on the cookie based on the MaxAge
|
||||||
|
// If MaxAge <= 0, we don't set the Expires attribute, making the cookie
|
||||||
|
// session-only.
|
||||||
|
if cs.maxAge > 0 {
|
||||||
|
cookie.Expires = time.Now().Add(
|
||||||
|
time.Duration(cs.maxAge) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the authenticated cookie to the response.
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
86
src/vendor/github.com/gorilla/csrf/store_legacy.go
generated
vendored
Normal file
86
src/vendor/github.com/gorilla/csrf/store_legacy.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// +build !go1.11
|
||||||
|
// file for compatibility with go versions prior to 1.11
|
||||||
|
|
||||||
|
package csrf
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/securecookie"
|
||||||
|
)
|
||||||
|
|
||||||
|
// store represents the session storage used for CSRF tokens.
|
||||||
|
type store interface {
|
||||||
|
// Get returns the real CSRF token from the store.
|
||||||
|
Get(*http.Request) ([]byte, error)
|
||||||
|
// Save stores the real CSRF token in the store and writes a
|
||||||
|
// cookie to the http.ResponseWriter.
|
||||||
|
// For non-cookie stores, the cookie should contain a unique (256 bit) ID
|
||||||
|
// or key that references the token in the backend store.
|
||||||
|
// csrf.GenerateRandomBytes is a helper function for generating secure IDs.
|
||||||
|
Save(token []byte, w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookieStore is a signed cookie session store for CSRF tokens.
|
||||||
|
type cookieStore struct {
|
||||||
|
name string
|
||||||
|
maxAge int
|
||||||
|
secure bool
|
||||||
|
httpOnly bool
|
||||||
|
path string
|
||||||
|
domain string
|
||||||
|
sc *securecookie.SecureCookie
|
||||||
|
sameSite SameSiteMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a CSRF token from the session cookie. It returns an empty token
|
||||||
|
// if decoding fails (e.g. HMAC validation fails or the named cookie doesn't exist).
|
||||||
|
func (cs *cookieStore) Get(r *http.Request) ([]byte, error) {
|
||||||
|
// Retrieve the cookie from the request
|
||||||
|
cookie, err := r.Cookie(cs.name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
token := make([]byte, tokenLength)
|
||||||
|
// Decode the HMAC authenticated cookie.
|
||||||
|
err = cs.sc.Decode(cs.name, cookie.Value, &token)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save stores the CSRF token in the session cookie.
|
||||||
|
func (cs *cookieStore) Save(token []byte, w http.ResponseWriter) error {
|
||||||
|
// Generate an encoded cookie value with the CSRF token.
|
||||||
|
encoded, err := cs.sc.Encode(cs.name, token)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: cs.name,
|
||||||
|
Value: encoded,
|
||||||
|
MaxAge: cs.maxAge,
|
||||||
|
HttpOnly: cs.httpOnly,
|
||||||
|
Secure: cs.secure,
|
||||||
|
Path: cs.path,
|
||||||
|
Domain: cs.domain,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Expires field on the cookie based on the MaxAge
|
||||||
|
// If MaxAge <= 0, we don't set the Expires attribute, making the cookie
|
||||||
|
// session-only.
|
||||||
|
if cs.maxAge > 0 {
|
||||||
|
cookie.Expires = time.Now().Add(
|
||||||
|
time.Duration(cs.maxAge) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the authenticated cookie to the response.
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
19
src/vendor/github.com/gorilla/securecookie/.travis.yml
generated
vendored
Normal file
19
src/vendor/github.com/gorilla/securecookie/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
language: go
|
||||||
|
sudo: false
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- go: 1.3
|
||||||
|
- go: 1.4
|
||||||
|
- go: 1.5
|
||||||
|
- go: 1.6
|
||||||
|
- go: 1.7
|
||||||
|
- go: tip
|
||||||
|
allow_failures:
|
||||||
|
- go: tip
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d .)
|
||||||
|
- go vet $(go list ./... | grep -v /vendor/)
|
||||||
|
- go test -v -race ./...
|
27
src/vendor/github.com/gorilla/securecookie/LICENSE
generated
vendored
Normal file
27
src/vendor/github.com/gorilla/securecookie/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
80
src/vendor/github.com/gorilla/securecookie/README.md
generated
vendored
Normal file
80
src/vendor/github.com/gorilla/securecookie/README.md
generated
vendored
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
securecookie
|
||||||
|
============
|
||||||
|
[![GoDoc](https://godoc.org/github.com/gorilla/securecookie?status.svg)](https://godoc.org/github.com/gorilla/securecookie) [![Build Status](https://travis-ci.org/gorilla/securecookie.png?branch=master)](https://travis-ci.org/gorilla/securecookie)
|
||||||
|
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/securecookie/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/securecookie?badge)
|
||||||
|
|
||||||
|
|
||||||
|
securecookie encodes and decodes authenticated and optionally encrypted
|
||||||
|
cookie values.
|
||||||
|
|
||||||
|
Secure cookies can't be forged, because their values are validated using HMAC.
|
||||||
|
When encrypted, the content is also inaccessible to malicious eyes. It is still
|
||||||
|
recommended that sensitive data not be stored in cookies, and that HTTPS be used
|
||||||
|
to prevent cookie [replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
To use it, first create a new SecureCookie instance:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Hash keys should be at least 32 bytes long
|
||||||
|
var hashKey = []byte("very-secret")
|
||||||
|
// Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
|
||||||
|
// Shorter keys may weaken the encryption used.
|
||||||
|
var blockKey = []byte("a-lot-secret")
|
||||||
|
var s = securecookie.New(hashKey, blockKey)
|
||||||
|
```
|
||||||
|
|
||||||
|
The hashKey is required, used to authenticate the cookie value using HMAC.
|
||||||
|
It is recommended to use a key with 32 or 64 bytes.
|
||||||
|
|
||||||
|
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
||||||
|
to not use encryption. If set, the length must correspond to the block size
|
||||||
|
of the encryption algorithm. For AES, used by default, valid lengths are
|
||||||
|
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||||
|
|
||||||
|
Strong keys can be created using the convenience function GenerateRandomKey().
|
||||||
|
|
||||||
|
Once a SecureCookie instance is set, use it to encode a cookie value:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
value := map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "cookie-name",
|
||||||
|
Value: encoded,
|
||||||
|
Path: "/",
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Later, use the same SecureCookie instance to decode and validate a cookie
|
||||||
|
value:
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
||||||
|
value := make(map[string]string)
|
||||||
|
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
||||||
|
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We stored a map[string]string, but secure cookies can hold any value that
|
||||||
|
can be encoded using `encoding/gob`. To store custom types, they must be
|
||||||
|
registered first using gob.Register(). For basic types this is not needed;
|
||||||
|
it works out of the box. An optional JSON encoder that uses `encoding/json` is
|
||||||
|
available for types compatible with JSON.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
BSD licensed. See the LICENSE file for details.
|
61
src/vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
Normal file
61
src/vendor/github.com/gorilla/securecookie/doc.go
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package securecookie encodes and decodes authenticated and optionally
|
||||||
|
encrypted cookie values.
|
||||||
|
|
||||||
|
Secure cookies can't be forged, because their values are validated using HMAC.
|
||||||
|
When encrypted, the content is also inaccessible to malicious eyes.
|
||||||
|
|
||||||
|
To use it, first create a new SecureCookie instance:
|
||||||
|
|
||||||
|
var hashKey = []byte("very-secret")
|
||||||
|
var blockKey = []byte("a-lot-secret")
|
||||||
|
var s = securecookie.New(hashKey, blockKey)
|
||||||
|
|
||||||
|
The hashKey is required, used to authenticate the cookie value using HMAC.
|
||||||
|
It is recommended to use a key with 32 or 64 bytes.
|
||||||
|
|
||||||
|
The blockKey is optional, used to encrypt the cookie value -- set it to nil
|
||||||
|
to not use encryption. If set, the length must correspond to the block size
|
||||||
|
of the encryption algorithm. For AES, used by default, valid lengths are
|
||||||
|
16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||||
|
|
||||||
|
Strong keys can be created using the convenience function GenerateRandomKey().
|
||||||
|
|
||||||
|
Once a SecureCookie instance is set, use it to encode a cookie value:
|
||||||
|
|
||||||
|
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
value := map[string]string{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
if encoded, err := s.Encode("cookie-name", value); err == nil {
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: "cookie-name",
|
||||||
|
Value: encoded,
|
||||||
|
Path: "/",
|
||||||
|
}
|
||||||
|
http.SetCookie(w, cookie)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Later, use the same SecureCookie instance to decode and validate a cookie
|
||||||
|
value:
|
||||||
|
|
||||||
|
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if cookie, err := r.Cookie("cookie-name"); err == nil {
|
||||||
|
value := make(map[string]string)
|
||||||
|
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
|
||||||
|
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
We stored a map[string]string, but secure cookies can hold any value that
|
||||||
|
can be encoded using encoding/gob. To store custom types, they must be
|
||||||
|
registered first using gob.Register(). For basic types this is not needed;
|
||||||
|
it works out of the box.
|
||||||
|
*/
|
||||||
|
package securecookie
|
25
src/vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
Normal file
25
src/vendor/github.com/gorilla/securecookie/fuzz.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// +build gofuzz
|
||||||
|
|
||||||
|
package securecookie
|
||||||
|
|
||||||
|
var hashKey = []byte("very-secret12345")
|
||||||
|
var blockKey = []byte("a-lot-secret1234")
|
||||||
|
var s = New(hashKey, blockKey)
|
||||||
|
|
||||||
|
type Cookie struct {
|
||||||
|
B bool
|
||||||
|
I int
|
||||||
|
S string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
datas := string(data)
|
||||||
|
var c Cookie
|
||||||
|
if err := s.Decode("fuzz", datas, &c); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if _, err := s.Encode("fuzz", c); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
646
src/vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
Normal file
646
src/vendor/github.com/gorilla/securecookie/securecookie.go
generated
vendored
Normal file
@ -0,0 +1,646 @@
|
|||||||
|
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package securecookie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/subtle"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"hash"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error is the interface of all errors returned by functions in this library.
|
||||||
|
type Error interface {
|
||||||
|
error
|
||||||
|
|
||||||
|
// IsUsage returns true for errors indicating the client code probably
|
||||||
|
// uses this library incorrectly. For example, the client may have
|
||||||
|
// failed to provide a valid hash key, or may have failed to configure
|
||||||
|
// the Serializer adequately for encoding value.
|
||||||
|
IsUsage() bool
|
||||||
|
|
||||||
|
// IsDecode returns true for errors indicating that a cookie could not
|
||||||
|
// be decoded and validated. Since cookies are usually untrusted
|
||||||
|
// user-provided input, errors of this type should be expected.
|
||||||
|
// Usually, the proper action is simply to reject the request.
|
||||||
|
IsDecode() bool
|
||||||
|
|
||||||
|
// IsInternal returns true for unexpected errors occurring in the
|
||||||
|
// securecookie implementation.
|
||||||
|
IsInternal() bool
|
||||||
|
|
||||||
|
// Cause, if it returns a non-nil value, indicates that this error was
|
||||||
|
// propagated from some underlying library. If this method returns nil,
|
||||||
|
// this error was raised directly by this library.
|
||||||
|
//
|
||||||
|
// Cause is provided principally for debugging/logging purposes; it is
|
||||||
|
// rare that application logic should perform meaningfully different
|
||||||
|
// logic based on Cause. See, for example, the caveats described on
|
||||||
|
// (MultiError).Cause().
|
||||||
|
Cause() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// errorType is a bitmask giving the error type(s) of an cookieError value.
|
||||||
|
type errorType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
usageError = errorType(1 << iota)
|
||||||
|
decodeError
|
||||||
|
internalError
|
||||||
|
)
|
||||||
|
|
||||||
|
type cookieError struct {
|
||||||
|
typ errorType
|
||||||
|
msg string
|
||||||
|
cause error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e cookieError) IsUsage() bool { return (e.typ & usageError) != 0 }
|
||||||
|
func (e cookieError) IsDecode() bool { return (e.typ & decodeError) != 0 }
|
||||||
|
func (e cookieError) IsInternal() bool { return (e.typ & internalError) != 0 }
|
||||||
|
|
||||||
|
func (e cookieError) Cause() error { return e.cause }
|
||||||
|
|
||||||
|
func (e cookieError) Error() string {
|
||||||
|
parts := []string{"securecookie: "}
|
||||||
|
if e.msg == "" {
|
||||||
|
parts = append(parts, "error")
|
||||||
|
} else {
|
||||||
|
parts = append(parts, e.msg)
|
||||||
|
}
|
||||||
|
if c := e.Cause(); c != nil {
|
||||||
|
parts = append(parts, " - caused by: ", c.Error())
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errGeneratingIV = cookieError{typ: internalError, msg: "failed to generate random iv"}
|
||||||
|
|
||||||
|
errNoCodecs = cookieError{typ: usageError, msg: "no codecs provided"}
|
||||||
|
errHashKeyNotSet = cookieError{typ: usageError, msg: "hash key is not set"}
|
||||||
|
errBlockKeyNotSet = cookieError{typ: usageError, msg: "block key is not set"}
|
||||||
|
errEncodedValueTooLong = cookieError{typ: usageError, msg: "the value is too long"}
|
||||||
|
|
||||||
|
errValueToDecodeTooLong = cookieError{typ: decodeError, msg: "the value is too long"}
|
||||||
|
errTimestampInvalid = cookieError{typ: decodeError, msg: "invalid timestamp"}
|
||||||
|
errTimestampTooNew = cookieError{typ: decodeError, msg: "timestamp is too new"}
|
||||||
|
errTimestampExpired = cookieError{typ: decodeError, msg: "expired timestamp"}
|
||||||
|
errDecryptionFailed = cookieError{typ: decodeError, msg: "the value could not be decrypted"}
|
||||||
|
errValueNotByte = cookieError{typ: decodeError, msg: "value not a []byte."}
|
||||||
|
errValueNotBytePtr = cookieError{typ: decodeError, msg: "value not a pointer to []byte."}
|
||||||
|
|
||||||
|
// ErrMacInvalid indicates that cookie decoding failed because the HMAC
|
||||||
|
// could not be extracted and verified. Direct use of this error
|
||||||
|
// variable is deprecated; it is public only for legacy compatibility,
|
||||||
|
// and may be privatized in the future, as it is rarely useful to
|
||||||
|
// distinguish between this error and other Error implementations.
|
||||||
|
ErrMacInvalid = cookieError{typ: decodeError, msg: "the value is not valid"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Codec defines an interface to encode and decode cookie values.
|
||||||
|
type Codec interface {
|
||||||
|
Encode(name string, value interface{}) (string, error)
|
||||||
|
Decode(name, value string, dst interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new SecureCookie.
|
||||||
|
//
|
||||||
|
// hashKey is required, used to authenticate values using HMAC. Create it using
|
||||||
|
// GenerateRandomKey(). It is recommended to use a key with 32 or 64 bytes.
|
||||||
|
//
|
||||||
|
// blockKey is optional, used to encrypt values. Create it using
|
||||||
|
// GenerateRandomKey(). The key length must correspond to the block size
|
||||||
|
// of the encryption algorithm. For AES, used by default, valid lengths are
|
||||||
|
// 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256.
|
||||||
|
// The default encoder used for cookie serialization is encoding/gob.
|
||||||
|
//
|
||||||
|
// Note that keys created using GenerateRandomKey() are not automatically
|
||||||
|
// persisted. New keys will be created when the application is restarted, and
|
||||||
|
// previously issued cookies will not be able to be decoded.
|
||||||
|
func New(hashKey, blockKey []byte) *SecureCookie {
|
||||||
|
s := &SecureCookie{
|
||||||
|
hashKey: hashKey,
|
||||||
|
blockKey: blockKey,
|
||||||
|
hashFunc: sha256.New,
|
||||||
|
maxAge: 86400 * 30,
|
||||||
|
maxLength: 4096,
|
||||||
|
sz: GobEncoder{},
|
||||||
|
}
|
||||||
|
if hashKey == nil {
|
||||||
|
s.err = errHashKeyNotSet
|
||||||
|
}
|
||||||
|
if blockKey != nil {
|
||||||
|
s.BlockFunc(aes.NewCipher)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// SecureCookie encodes and decodes authenticated and optionally encrypted
|
||||||
|
// cookie values.
|
||||||
|
type SecureCookie struct {
|
||||||
|
hashKey []byte
|
||||||
|
hashFunc func() hash.Hash
|
||||||
|
blockKey []byte
|
||||||
|
block cipher.Block
|
||||||
|
maxLength int
|
||||||
|
maxAge int64
|
||||||
|
minAge int64
|
||||||
|
err error
|
||||||
|
sz Serializer
|
||||||
|
// For testing purposes, the function that returns the current timestamp.
|
||||||
|
// If not set, it will use time.Now().UTC().Unix().
|
||||||
|
timeFunc func() int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serializer provides an interface for providing custom serializers for cookie
|
||||||
|
// values.
|
||||||
|
type Serializer interface {
|
||||||
|
Serialize(src interface{}) ([]byte, error)
|
||||||
|
Deserialize(src []byte, dst interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GobEncoder encodes cookie values using encoding/gob. This is the simplest
|
||||||
|
// encoder and can handle complex types via gob.Register.
|
||||||
|
type GobEncoder struct{}
|
||||||
|
|
||||||
|
// JSONEncoder encodes cookie values using encoding/json. Users who wish to
|
||||||
|
// encode complex types need to satisfy the json.Marshaller and
|
||||||
|
// json.Unmarshaller interfaces.
|
||||||
|
type JSONEncoder struct{}
|
||||||
|
|
||||||
|
// NopEncoder does not encode cookie values, and instead simply accepts a []byte
|
||||||
|
// (as an interface{}) and returns a []byte. This is particularly useful when
|
||||||
|
// you encoding an object upstream and do not wish to re-encode it.
|
||||||
|
type NopEncoder struct{}
|
||||||
|
|
||||||
|
// MaxLength restricts the maximum length, in bytes, for the cookie value.
|
||||||
|
//
|
||||||
|
// Default is 4096, which is the maximum value accepted by Internet Explorer.
|
||||||
|
func (s *SecureCookie) MaxLength(value int) *SecureCookie {
|
||||||
|
s.maxLength = value
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxAge restricts the maximum age, in seconds, for the cookie value.
|
||||||
|
//
|
||||||
|
// Default is 86400 * 30. Set it to 0 for no restriction.
|
||||||
|
func (s *SecureCookie) MaxAge(value int) *SecureCookie {
|
||||||
|
s.maxAge = int64(value)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MinAge restricts the minimum age, in seconds, for the cookie value.
|
||||||
|
//
|
||||||
|
// Default is 0 (no restriction).
|
||||||
|
func (s *SecureCookie) MinAge(value int) *SecureCookie {
|
||||||
|
s.minAge = int64(value)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashFunc sets the hash function used to create HMAC.
|
||||||
|
//
|
||||||
|
// Default is crypto/sha256.New.
|
||||||
|
func (s *SecureCookie) HashFunc(f func() hash.Hash) *SecureCookie {
|
||||||
|
s.hashFunc = f
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockFunc sets the encryption function used to create a cipher.Block.
|
||||||
|
//
|
||||||
|
// Default is crypto/aes.New.
|
||||||
|
func (s *SecureCookie) BlockFunc(f func([]byte) (cipher.Block, error)) *SecureCookie {
|
||||||
|
if s.blockKey == nil {
|
||||||
|
s.err = errBlockKeyNotSet
|
||||||
|
} else if block, err := f(s.blockKey); err == nil {
|
||||||
|
s.block = block
|
||||||
|
} else {
|
||||||
|
s.err = cookieError{cause: err, typ: usageError}
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding sets the encoding/serialization method for cookies.
|
||||||
|
//
|
||||||
|
// Default is encoding/gob. To encode special structures using encoding/gob,
|
||||||
|
// they must be registered first using gob.Register().
|
||||||
|
func (s *SecureCookie) SetSerializer(sz Serializer) *SecureCookie {
|
||||||
|
s.sz = sz
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes a cookie value.
|
||||||
|
//
|
||||||
|
// It serializes, optionally encrypts, signs with a message authentication code,
|
||||||
|
// and finally encodes the value.
|
||||||
|
//
|
||||||
|
// The name argument is the cookie name. It is stored with the encoded value.
|
||||||
|
// The value argument is the value to be encoded. It can be any value that can
|
||||||
|
// be encoded using the currently selected serializer; see SetSerializer().
|
||||||
|
//
|
||||||
|
// It is the client's responsibility to ensure that value, when encoded using
|
||||||
|
// the current serialization/encryption settings on s and then base64-encoded,
|
||||||
|
// is shorter than the maximum permissible length.
|
||||||
|
func (s *SecureCookie) Encode(name string, value interface{}) (string, error) {
|
||||||
|
if s.err != nil {
|
||||||
|
return "", s.err
|
||||||
|
}
|
||||||
|
if s.hashKey == nil {
|
||||||
|
s.err = errHashKeyNotSet
|
||||||
|
return "", s.err
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
var b []byte
|
||||||
|
// 1. Serialize.
|
||||||
|
if b, err = s.sz.Serialize(value); err != nil {
|
||||||
|
return "", cookieError{cause: err, typ: usageError}
|
||||||
|
}
|
||||||
|
// 2. Encrypt (optional).
|
||||||
|
if s.block != nil {
|
||||||
|
if b, err = encrypt(s.block, b); err != nil {
|
||||||
|
return "", cookieError{cause: err, typ: usageError}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b = encode(b)
|
||||||
|
// 3. Create MAC for "name|date|value". Extra pipe to be used later.
|
||||||
|
b = []byte(fmt.Sprintf("%s|%d|%s|", name, s.timestamp(), b))
|
||||||
|
mac := createMac(hmac.New(s.hashFunc, s.hashKey), b[:len(b)-1])
|
||||||
|
// Append mac, remove name.
|
||||||
|
b = append(b, mac...)[len(name)+1:]
|
||||||
|
// 4. Encode to base64.
|
||||||
|
b = encode(b)
|
||||||
|
// 5. Check length.
|
||||||
|
if s.maxLength != 0 && len(b) > s.maxLength {
|
||||||
|
return "", errEncodedValueTooLong
|
||||||
|
}
|
||||||
|
// Done.
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a cookie value.
|
||||||
|
//
|
||||||
|
// It decodes, verifies a message authentication code, optionally decrypts and
|
||||||
|
// finally deserializes the value.
|
||||||
|
//
|
||||||
|
// The name argument is the cookie name. It must be the same name used when
|
||||||
|
// it was stored. The value argument is the encoded cookie value. The dst
|
||||||
|
// argument is where the cookie will be decoded. It must be a pointer.
|
||||||
|
func (s *SecureCookie) Decode(name, value string, dst interface{}) error {
|
||||||
|
if s.err != nil {
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
if s.hashKey == nil {
|
||||||
|
s.err = errHashKeyNotSet
|
||||||
|
return s.err
|
||||||
|
}
|
||||||
|
// 1. Check length.
|
||||||
|
if s.maxLength != 0 && len(value) > s.maxLength {
|
||||||
|
return errValueToDecodeTooLong
|
||||||
|
}
|
||||||
|
// 2. Decode from base64.
|
||||||
|
b, err := decode([]byte(value))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 3. Verify MAC. Value is "date|value|mac".
|
||||||
|
parts := bytes.SplitN(b, []byte("|"), 3)
|
||||||
|
if len(parts) != 3 {
|
||||||
|
return ErrMacInvalid
|
||||||
|
}
|
||||||
|
h := hmac.New(s.hashFunc, s.hashKey)
|
||||||
|
b = append([]byte(name+"|"), b[:len(b)-len(parts[2])-1]...)
|
||||||
|
if err = verifyMac(h, b, parts[2]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 4. Verify date ranges.
|
||||||
|
var t1 int64
|
||||||
|
if t1, err = strconv.ParseInt(string(parts[0]), 10, 64); err != nil {
|
||||||
|
return errTimestampInvalid
|
||||||
|
}
|
||||||
|
t2 := s.timestamp()
|
||||||
|
if s.minAge != 0 && t1 > t2-s.minAge {
|
||||||
|
return errTimestampTooNew
|
||||||
|
}
|
||||||
|
if s.maxAge != 0 && t1 < t2-s.maxAge {
|
||||||
|
return errTimestampExpired
|
||||||
|
}
|
||||||
|
// 5. Decrypt (optional).
|
||||||
|
b, err = decode(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.block != nil {
|
||||||
|
if b, err = decrypt(s.block, b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 6. Deserialize.
|
||||||
|
if err = s.sz.Deserialize(b, dst); err != nil {
|
||||||
|
return cookieError{cause: err, typ: decodeError}
|
||||||
|
}
|
||||||
|
// Done.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// timestamp returns the current timestamp, in seconds.
|
||||||
|
//
|
||||||
|
// For testing purposes, the function that generates the timestamp can be
|
||||||
|
// overridden. If not set, it will return time.Now().UTC().Unix().
|
||||||
|
func (s *SecureCookie) timestamp() int64 {
|
||||||
|
if s.timeFunc == nil {
|
||||||
|
return time.Now().UTC().Unix()
|
||||||
|
}
|
||||||
|
return s.timeFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authentication -------------------------------------------------------------
|
||||||
|
|
||||||
|
// createMac creates a message authentication code (MAC).
|
||||||
|
func createMac(h hash.Hash, value []byte) []byte {
|
||||||
|
h.Write(value)
|
||||||
|
return h.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verifyMac verifies that a message authentication code (MAC) is valid.
|
||||||
|
func verifyMac(h hash.Hash, value []byte, mac []byte) error {
|
||||||
|
mac2 := createMac(h, value)
|
||||||
|
// Check that both MACs are of equal length, as subtle.ConstantTimeCompare
|
||||||
|
// does not do this prior to Go 1.4.
|
||||||
|
if len(mac) == len(mac2) && subtle.ConstantTimeCompare(mac, mac2) == 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ErrMacInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encryption -----------------------------------------------------------------
|
||||||
|
|
||||||
|
// encrypt encrypts a value using the given block in counter mode.
|
||||||
|
//
|
||||||
|
// A random initialization vector (http://goo.gl/zF67k) with the length of the
|
||||||
|
// block size is prepended to the resulting ciphertext.
|
||||||
|
func encrypt(block cipher.Block, value []byte) ([]byte, error) {
|
||||||
|
iv := GenerateRandomKey(block.BlockSize())
|
||||||
|
if iv == nil {
|
||||||
|
return nil, errGeneratingIV
|
||||||
|
}
|
||||||
|
// Encrypt it.
|
||||||
|
stream := cipher.NewCTR(block, iv)
|
||||||
|
stream.XORKeyStream(value, value)
|
||||||
|
// Return iv + ciphertext.
|
||||||
|
return append(iv, value...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt decrypts a value using the given block in counter mode.
|
||||||
|
//
|
||||||
|
// The value to be decrypted must be prepended by a initialization vector
|
||||||
|
// (http://goo.gl/zF67k) with the length of the block size.
|
||||||
|
func decrypt(block cipher.Block, value []byte) ([]byte, error) {
|
||||||
|
size := block.BlockSize()
|
||||||
|
if len(value) > size {
|
||||||
|
// Extract iv.
|
||||||
|
iv := value[:size]
|
||||||
|
// Extract ciphertext.
|
||||||
|
value = value[size:]
|
||||||
|
// Decrypt it.
|
||||||
|
stream := cipher.NewCTR(block, iv)
|
||||||
|
stream.XORKeyStream(value, value)
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return nil, errDecryptionFailed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialization --------------------------------------------------------------
|
||||||
|
|
||||||
|
// Serialize encodes a value using gob.
|
||||||
|
func (e GobEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := gob.NewEncoder(buf)
|
||||||
|
if err := enc.Encode(src); err != nil {
|
||||||
|
return nil, cookieError{cause: err, typ: usageError}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize decodes a value using gob.
|
||||||
|
func (e GobEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||||
|
dec := gob.NewDecoder(bytes.NewBuffer(src))
|
||||||
|
if err := dec.Decode(dst); err != nil {
|
||||||
|
return cookieError{cause: err, typ: decodeError}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize encodes a value using encoding/json.
|
||||||
|
func (e JSONEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
if err := enc.Encode(src); err != nil {
|
||||||
|
return nil, cookieError{cause: err, typ: usageError}
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize decodes a value using encoding/json.
|
||||||
|
func (e JSONEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||||
|
dec := json.NewDecoder(bytes.NewReader(src))
|
||||||
|
if err := dec.Decode(dst); err != nil {
|
||||||
|
return cookieError{cause: err, typ: decodeError}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize passes a []byte through as-is.
|
||||||
|
func (e NopEncoder) Serialize(src interface{}) ([]byte, error) {
|
||||||
|
if b, ok := src.([]byte); ok {
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errValueNotByte
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize passes a []byte through as-is.
|
||||||
|
func (e NopEncoder) Deserialize(src []byte, dst interface{}) error {
|
||||||
|
if dat, ok := dst.(*[]byte); ok {
|
||||||
|
*dat = src
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errValueNotBytePtr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoding -------------------------------------------------------------------
|
||||||
|
|
||||||
|
// encode encodes a value using base64.
|
||||||
|
func encode(value []byte) []byte {
|
||||||
|
encoded := make([]byte, base64.URLEncoding.EncodedLen(len(value)))
|
||||||
|
base64.URLEncoding.Encode(encoded, value)
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode decodes a cookie using base64.
|
||||||
|
func decode(value []byte) ([]byte, error) {
|
||||||
|
decoded := make([]byte, base64.URLEncoding.DecodedLen(len(value)))
|
||||||
|
b, err := base64.URLEncoding.Decode(decoded, value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, cookieError{cause: err, typ: decodeError, msg: "base64 decode failed"}
|
||||||
|
}
|
||||||
|
return decoded[:b], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers --------------------------------------------------------------------
|
||||||
|
|
||||||
|
// GenerateRandomKey creates a random key with the given length in bytes.
|
||||||
|
// On failure, returns nil.
|
||||||
|
//
|
||||||
|
// Callers should explicitly check for the possibility of a nil return, treat
|
||||||
|
// it as a failure of the system random number generator, and not continue.
|
||||||
|
func GenerateRandomKey(length int) []byte {
|
||||||
|
k := make([]byte, length)
|
||||||
|
if _, err := io.ReadFull(rand.Reader, k); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodecsFromPairs returns a slice of SecureCookie instances.
|
||||||
|
//
|
||||||
|
// It is a convenience function to create a list of codecs for key rotation. Note
|
||||||
|
// that the generated Codecs will have the default options applied: callers
|
||||||
|
// should iterate over each Codec and type-assert the underlying *SecureCookie to
|
||||||
|
// change these.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// codecs := securecookie.CodecsFromPairs(
|
||||||
|
// []byte("new-hash-key"),
|
||||||
|
// []byte("new-block-key"),
|
||||||
|
// []byte("old-hash-key"),
|
||||||
|
// []byte("old-block-key"),
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// // Modify each instance.
|
||||||
|
// for _, s := range codecs {
|
||||||
|
// if cookie, ok := s.(*securecookie.SecureCookie); ok {
|
||||||
|
// cookie.MaxAge(86400 * 7)
|
||||||
|
// cookie.SetSerializer(securecookie.JSONEncoder{})
|
||||||
|
// cookie.HashFunc(sha512.New512_256)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func CodecsFromPairs(keyPairs ...[]byte) []Codec {
|
||||||
|
codecs := make([]Codec, len(keyPairs)/2+len(keyPairs)%2)
|
||||||
|
for i := 0; i < len(keyPairs); i += 2 {
|
||||||
|
var blockKey []byte
|
||||||
|
if i+1 < len(keyPairs) {
|
||||||
|
blockKey = keyPairs[i+1]
|
||||||
|
}
|
||||||
|
codecs[i/2] = New(keyPairs[i], blockKey)
|
||||||
|
}
|
||||||
|
return codecs
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeMulti encodes a cookie value using a group of codecs.
|
||||||
|
//
|
||||||
|
// The codecs are tried in order. Multiple codecs are accepted to allow
|
||||||
|
// key rotation.
|
||||||
|
//
|
||||||
|
// On error, may return a MultiError.
|
||||||
|
func EncodeMulti(name string, value interface{}, codecs ...Codec) (string, error) {
|
||||||
|
if len(codecs) == 0 {
|
||||||
|
return "", errNoCodecs
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors MultiError
|
||||||
|
for _, codec := range codecs {
|
||||||
|
encoded, err := codec.Encode(name, value)
|
||||||
|
if err == nil {
|
||||||
|
return encoded, nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
return "", errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMulti decodes a cookie value using a group of codecs.
|
||||||
|
//
|
||||||
|
// The codecs are tried in order. Multiple codecs are accepted to allow
|
||||||
|
// key rotation.
|
||||||
|
//
|
||||||
|
// On error, may return a MultiError.
|
||||||
|
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
|
||||||
|
if len(codecs) == 0 {
|
||||||
|
return errNoCodecs
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors MultiError
|
||||||
|
for _, codec := range codecs {
|
||||||
|
err := codec.Decode(name, value, dst)
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
errors = append(errors, err)
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// MultiError groups multiple errors.
|
||||||
|
type MultiError []error
|
||||||
|
|
||||||
|
func (m MultiError) IsUsage() bool { return m.any(func(e Error) bool { return e.IsUsage() }) }
|
||||||
|
func (m MultiError) IsDecode() bool { return m.any(func(e Error) bool { return e.IsDecode() }) }
|
||||||
|
func (m MultiError) IsInternal() bool { return m.any(func(e Error) bool { return e.IsInternal() }) }
|
||||||
|
|
||||||
|
// Cause returns nil for MultiError; there is no unique underlying cause in the
|
||||||
|
// general case.
|
||||||
|
//
|
||||||
|
// Note: we could conceivably return a non-nil Cause only when there is exactly
|
||||||
|
// one child error with a Cause. However, it would be brittle for client code
|
||||||
|
// to rely on the arity of causes inside a MultiError, so we have opted not to
|
||||||
|
// provide this functionality. Clients which really wish to access the Causes
|
||||||
|
// of the underlying errors are free to iterate through the errors themselves.
|
||||||
|
func (m MultiError) Cause() error { return nil }
|
||||||
|
|
||||||
|
func (m MultiError) Error() string {
|
||||||
|
s, n := "", 0
|
||||||
|
for _, e := range m {
|
||||||
|
if e != nil {
|
||||||
|
if n == 0 {
|
||||||
|
s = e.Error()
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch n {
|
||||||
|
case 0:
|
||||||
|
return "(0 errors)"
|
||||||
|
case 1:
|
||||||
|
return s
|
||||||
|
case 2:
|
||||||
|
return s + " (and 1 other error)"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// any returns true if any element of m is an Error for which pred returns true.
|
||||||
|
func (m MultiError) any(pred func(Error) bool) bool {
|
||||||
|
for _, e := range m {
|
||||||
|
if ourErr, ok := e.(Error); ok && pred(ourErr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
4
src/vendor/modules.txt
vendored
4
src/vendor/modules.txt
vendored
@ -189,10 +189,14 @@ github.com/google/go-querystring/query
|
|||||||
github.com/google/gofuzz
|
github.com/google/gofuzz
|
||||||
# github.com/google/uuid v1.1.1
|
# github.com/google/uuid v1.1.1
|
||||||
github.com/google/uuid
|
github.com/google/uuid
|
||||||
|
# github.com/gorilla/csrf v1.6.2
|
||||||
|
github.com/gorilla/csrf
|
||||||
# github.com/gorilla/handlers v1.3.0
|
# github.com/gorilla/handlers v1.3.0
|
||||||
github.com/gorilla/handlers
|
github.com/gorilla/handlers
|
||||||
# github.com/gorilla/mux v1.7.2
|
# github.com/gorilla/mux v1.7.2
|
||||||
github.com/gorilla/mux
|
github.com/gorilla/mux
|
||||||
|
# github.com/gorilla/securecookie v1.1.1
|
||||||
|
github.com/gorilla/securecookie
|
||||||
# github.com/graph-gophers/dataloader v5.0.0+incompatible
|
# github.com/graph-gophers/dataloader v5.0.0+incompatible
|
||||||
github.com/graph-gophers/dataloader
|
github.com/graph-gophers/dataloader
|
||||||
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
# github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af
|
||||||
|
Loading…
Reference in New Issue
Block a user