1
0
mirror of https://github.com/labstack/echo.git synced 2024-12-24 20:14:31 +02:00
Signed-off-by: Vishal Rana <vr@labstack.com>
This commit is contained in:
Vishal Rana 2016-05-02 22:41:07 -07:00
parent c31a524d05
commit f0526349ff
9 changed files with 95 additions and 120 deletions

View File

@ -87,7 +87,7 @@ type (
// Cookie returns the named cookie provided in the request. // Cookie returns the named cookie provided in the request.
// It is an alias for `engine.Request#Cookie()`. // It is an alias for `engine.Request#Cookie()`.
Cookie(string) engine.Cookie Cookie(string) (engine.Cookie, error)
// SetCookie adds a `Set-Cookie` header in HTTP response. // SetCookie adds a `Set-Cookie` header in HTTP response.
// It is an alias for `engine.Response#SetCookie()`. // It is an alias for `engine.Response#SetCookie()`.
@ -295,7 +295,7 @@ func (c *context) MultipartForm() (*multipart.Form, error) {
return c.request.MultipartForm() return c.request.MultipartForm()
} }
func (c *context) Cookie(name string) engine.Cookie { func (c *context) Cookie(name string) (engine.Cookie, error) {
return c.request.Cookie(name) return c.request.Cookie(name)
} }

View File

@ -186,9 +186,11 @@ func TestContextCookie(t *testing.T) {
c := e.NewContext(req, rec).(*context) c := e.NewContext(req, rec).(*context)
// Read single // Read single
cookie := c.Cookie("theme") cookie, err := c.Cookie("theme")
if assert.NoError(t, err) {
assert.Equal(t, "theme", cookie.Name()) assert.Equal(t, "theme", cookie.Name())
assert.Equal(t, "light", cookie.Value()) assert.Equal(t, "light", cookie.Value())
}
// Read multiple // Read multiple
for _, cookie := range c.Cookies() { for _, cookie := range c.Cookies() {

View File

@ -166,6 +166,13 @@ const (
HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials" HeaderAccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers" HeaderAccessControlExposeHeaders = "Access-Control-Expose-Headers"
HeaderAccessControlMaxAge = "Access-Control-Max-Age" HeaderAccessControlMaxAge = "Access-Control-Max-Age"
// Security
HeaderStrictTransportSecurity = "Strict-Transport-Security"
HeaderXContentTypeOptions = "X-Content-Type-Options"
HeaderXXSSProtection = "X-XSS-Protection"
HeaderXFrameOptions = "X-Frame-Options"
HeaderContentSecurityPolicy = "Content-Security-Policy"
) )
var ( var (
@ -191,6 +198,7 @@ var (
ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge) ErrStatusRequestEntityTooLarge = NewHTTPError(http.StatusRequestEntityTooLarge)
ErrRendererNotRegistered = errors.New("renderer not registered") ErrRendererNotRegistered = errors.New("renderer not registered")
ErrInvalidRedirectCode = errors.New("invalid redirect status code") ErrInvalidRedirectCode = errors.New("invalid redirect status code")
ErrCookieNotFound = errors.New("cookie not found")
) )
// Error handlers // Error handlers

View File

@ -85,7 +85,7 @@ type (
MultipartForm() (*multipart.Form, error) MultipartForm() (*multipart.Form, error)
// Cookie returns the named cookie provided in the request. // Cookie returns the named cookie provided in the request.
Cookie(string) Cookie Cookie(string) (Cookie, error)
// Cookies returns the HTTP cookies sent with the request. // Cookies returns the HTTP cookies sent with the request.
Cookies() []Cookie Cookies() []Cookie

View File

@ -7,6 +7,7 @@ import (
"io" "io"
"mime/multipart" "mime/multipart"
"github.com/labstack/echo"
"github.com/labstack/echo/engine" "github.com/labstack/echo/engine"
"github.com/labstack/gommon/log" "github.com/labstack/gommon/log"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
@ -128,11 +129,15 @@ func (r *Request) MultipartForm() (*multipart.Form, error) {
} }
// Cookie implements `engine.Request#Cookie` function. // Cookie implements `engine.Request#Cookie` function.
func (r *Request) Cookie(name string) engine.Cookie { func (r *Request) Cookie(name string) (engine.Cookie, error) {
c := new(fasthttp.Cookie) c := new(fasthttp.Cookie)
c.SetKey(name) c.SetKey(name)
c.ParseBytes(r.Request.Header.Cookie(name)) b := r.Request.Header.Cookie(name)
return &Cookie{c} if b == nil {
return nil, echo.ErrCookieNotFound
}
c.ParseBytes(b)
return &Cookie{c}, nil
} }
// Cookies implements `engine.Request#Cookies` function. // Cookies implements `engine.Request#Cookies` function.

View File

@ -153,9 +153,12 @@ func (r *Request) MultipartForm() (*multipart.Form, error) {
} }
// Cookie implements `engine.Request#Cookie` function. // Cookie implements `engine.Request#Cookie` function.
func (r *Request) Cookie(name string) engine.Cookie { func (r *Request) Cookie(name string) (engine.Cookie, error) {
c, _ := r.Request.Cookie(name) c, err := r.Request.Cookie(name)
return &Cookie{c} if err != nil {
return nil, echo.ErrCookieNotFound
}
return &Cookie{c}, nil
} }
// Cookies implements `engine.Request#Cookies` function. // Cookies implements `engine.Request#Cookies` function.

View File

@ -8,32 +8,19 @@ import (
type ( type (
SecureConfig struct { SecureConfig struct {
STSMaxAge int64 DisableXSSProtection bool
STSIncludeSubdomains bool DisableContentTypeNosniff bool
FrameDeny bool XFrameOptions string
FrameOptionsValue string DisableHSTSIncludeSubdomains bool
ContentTypeNosniff bool HSTSMaxAge int
XssProtected bool
XssProtectionValue string
ContentSecurityPolicy string ContentSecurityPolicy string
DisableProdCheck bool
} }
) )
var ( var (
DefaultSecureConfig = SecureConfig{} DefaultSecureConfig = SecureConfig{
) XFrameOptions: "SAMEORIGIN",
}
const (
stsHeader = "Strict-Transport-Security"
stsSubdomainString = "; includeSubdomains"
frameOptionsHeader = "X-Frame-Options"
frameOptionsValue = "DENY"
contentTypeHeader = "X-Content-Type-Options"
contentTypeValue = "nosniff"
xssProtectionHeader = "X-XSS-Protection"
xssProtectionValue = "1; mode=block"
cspHeader = "Content-Security-Policy"
) )
func Secure() echo.MiddlewareFunc { func Secure() echo.MiddlewareFunc {
@ -43,51 +30,26 @@ func Secure() echo.MiddlewareFunc {
func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc { func SecureWithConfig(config SecureConfig) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc { return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error { return func(c echo.Context) error {
setFrameOptions(c, config) if !config.DisableXSSProtection {
setContentTypeOptions(c, config) c.Response().Header().Set(echo.HeaderXXSSProtection, "1; mode=block")
setXssProtection(c, config) }
setSTS(c, config) if !config.DisableContentTypeNosniff {
setCSP(c, config) c.Response().Header().Set(echo.HeaderXContentTypeOptions, "nosniff")
}
if config.XFrameOptions != "" {
c.Response().Header().Set(echo.HeaderXFrameOptions, config.XFrameOptions)
}
if config.HSTSMaxAge != 0 {
subdomains := ""
if !config.DisableHSTSIncludeSubdomains {
subdomains = "; includeSubdomains"
}
c.Response().Header().Set(echo.HeaderStrictTransportSecurity, fmt.Sprintf("max-age=%d%s", config.HSTSMaxAge, subdomains))
}
if config.ContentSecurityPolicy != "" {
c.Response().Header().Set(echo.HeaderContentSecurityPolicy, config.ContentSecurityPolicy)
}
return next(c) return next(c)
} }
} }
} }
func setFrameOptions(c echo.Context, opts SecureConfig) {
if opts.FrameOptionsValue != "" {
c.Response().Header().Set(frameOptionsHeader, opts.FrameOptionsValue)
} else if opts.FrameDeny {
c.Response().Header().Set(frameOptionsHeader, frameOptionsValue)
}
}
func setContentTypeOptions(c echo.Context, opts SecureConfig) {
if opts.ContentTypeNosniff {
c.Response().Header().Set(contentTypeHeader, contentTypeValue)
}
}
func setXssProtection(c echo.Context, opts SecureConfig) {
if opts.XssProtectionValue != "" {
c.Response().Header().Set(xssProtectionHeader, opts.XssProtectionValue)
} else if opts.XssProtected {
c.Response().Header().Set(xssProtectionHeader, xssProtectionValue)
}
}
func setSTS(c echo.Context, opts SecureConfig) {
if opts.STSMaxAge != 0 && opts.DisableProdCheck {
subDomains := ""
if opts.STSIncludeSubdomains {
subDomains = stsSubdomainString
}
c.Response().Header().Set(stsHeader, fmt.Sprintf("max-age=%d%s", opts.STSMaxAge, subDomains))
}
}
func setCSP(c echo.Context, opts SecureConfig) {
if opts.ContentSecurityPolicy != "" {
c.Response().Header().Set(cspHeader, opts.ContentSecurityPolicy)
}
}

View File

@ -1,41 +1,32 @@
package middleware package middleware
import ( // func TestSecureWithConfig(t *testing.T) {
"net/http" // e := echo.New()
"testing" //
// config := SecureConfig{
"github.com/labstack/echo" // STSMaxAge: 100,
"github.com/labstack/echo/test" // STSIncludeSubdomains: true,
"github.com/stretchr/testify/assert" // FrameDeny: true,
) // FrameOptionsValue: "",
// ContentTypeNosniff: true,
func TestSecureWithConfig(t *testing.T) { // XssProtected: true,
e := echo.New() // XssProtectionValue: "",
// ContentSecurityPolicy: "default-src 'self'",
config := SecureConfig{ // DisableProdCheck: true,
STSMaxAge: 100, // }
STSIncludeSubdomains: true, // secure := SecureWithConfig(config)
FrameDeny: true, // h := secure(func(c echo.Context) error {
FrameOptionsValue: "", // return c.String(http.StatusOK, "test")
ContentTypeNosniff: true, // })
XssProtected: true, //
XssProtectionValue: "", // rq := test.NewRequest(echo.GET, "/", nil)
ContentSecurityPolicy: "default-src 'self'", // rc := test.NewResponseRecorder()
DisableProdCheck: true, // c := e.NewContext(rq, rc)
} // h(c)
secure := SecureWithConfig(config) //
h := secure(func(c echo.Context) error { // assert.Equal(t, "max-age=100; includeSubdomains", rc.Header().Get(stsHeader))
return c.String(http.StatusOK, "test") // assert.Equal(t, "DENY", rc.Header().Get(frameOptionsHeader))
}) // assert.Equal(t, "nosniff", rc.Header().Get(contentTypeHeader))
// assert.Equal(t, xssProtectionValue, rc.Header().Get(xssProtectionHeader))
rq := test.NewRequest(echo.GET, "/", nil) // assert.Equal(t, "default-src 'self'", rc.Header().Get(cspHeader))
rc := test.NewResponseRecorder() // }
c := e.NewContext(rq, rc)
h(c)
assert.Equal(t, "max-age=100; includeSubdomains", rc.Header().Get(stsHeader))
assert.Equal(t, "DENY", rc.Header().Get(frameOptionsHeader))
assert.Equal(t, "nosniff", rc.Header().Get(contentTypeHeader))
assert.Equal(t, xssProtectionValue, rc.Header().Get(xssProtectionHeader))
assert.Equal(t, "default-src 'self'", rc.Header().Get(cspHeader))
}

View File

@ -1,6 +1,7 @@
package test package test
import ( import (
"errors"
"io" "io"
"io/ioutil" "io/ioutil"
"mime/multipart" "mime/multipart"
@ -130,9 +131,12 @@ func (r *Request) MultipartForm() (*multipart.Form, error) {
return r.request.MultipartForm, err return r.request.MultipartForm, err
} }
func (r *Request) Cookie(name string) engine.Cookie { func (r *Request) Cookie(name string) (engine.Cookie, error) {
c, _ := r.request.Cookie(name) c, err := r.request.Cookie(name)
return &Cookie{c} if err != nil {
return nil, errors.New("cookie not found")
}
return &Cookie{c}, nil
} }
// Cookies implements `engine.Request#Cookies` function. // Cookies implements `engine.Request#Cookies` function.