Secure-by-default HTTP servers in Go.
hackersatcambridge/hac-website 24
The Hackers at Cambridge Website Repo
Implementation of a simple dummy server and a test harness that mocks the network stack. The mini-projects were done in order to gain familiarity with Golang
C Tick as part of Cambridge Computer Science Part IB course that analyses and removes IP and TCP headers from log files
My attempt on scripts presented in the DTrace tutorials from dtrace.org
FreeBSD src tree http://www.FreeBSD.org/
Secure-by-default HTTP servers in Go.
A simple package to parse reports coming from browsers implementing the Reporting API
issue commentgoogle/go-safeweb
Another article that provides useful information on timeouts is this one
Thing to keep in mind:
- we need to set either
ReadTimeout(header + body) orReadHeaderTimeoutto prevent server resource exhaustation and DoS attacks - the Server's
Handlerfield should never be nil, but set to an instantiatedServeMuxas otherwise handlers will be registered on thehttp.DefaultServeMuxwhich is a global that all imported packages (and imports of imports) have access to and this can lead to information leakages - we need to disallow the usage of
http.ListenAndServeorhttp.Serveand could create equivalents with sane defaults
comment created time in an hour
Pull request review commentgoogle/go-safeweb
import ( "time" ) +// Drivers is a tyny wrapper for https://pkg.go.dev/sql#Drivers
tiny (here and below)
comment created time in 9 days
Pull request review commentgoogle/go-safeweb
import ( "time" ) +// Drivers is a tyny wrapper for https://pkg.go.dev/sql#Drivers func Drivers() []string { return sql.Drivers() } +// Register is a tyny wrapper for https://pkg.go.dev/sql#Register func Register(name string, driver driver.Driver) { sql.Register(name, driver) } +// ColumnType is a tyny wrapper for https://pkg.go.dev/sql#ColumnType type ColumnType = sql.ColumnType++// DBStats is a tyny wrapper for https://pkg.go.dev/sql#DBStats type DBStats = sql.DBStats++// IsolationLevel is a tyny wrapper for https://pkg.go.dev/sql#IsolationLevel type IsolationLevel = sql.IsolationLevel++// NamedArg is a tyny wrapper for https://pkg.go.dev/sql#NamedArg type NamedArg = sql.NamedArg++// NullBool is a tyny wrapper for https://pkg.go.dev/sql#NullBool type NullBool = sql.NullBool++// NullFloat64 is a tyny wrapper for https://pkg.go.dev/sql#NullFloat64 type NullFloat64 = sql.NullFloat64++// NullInt32 is a tyny wrapper for https://pkg.go.dev/sql#NullInt32 type NullInt32 = sql.NullInt32++// NullInt64 is a tyny wrapper for https://pkg.go.dev/sql#NullInt64 type NullInt64 = sql.NullInt64++// NullString is a tyny wrapper for https://pkg.go.dev/sql#NullString type NullString = sql.NullString++// NullTime is a tyny wrapper for https://pkg.go.dev/sql#NullTime type NullTime = sql.NullTime++// Out is a tyny wrapper for https://pkg.go.dev/sql#Out type Out = sql.Out++// RawBytes is a tyny wrapper for https://pkg.go.dev/sql#RawBytes type RawBytes = sql.RawBytes++// Result is a tyny wrapper for https://pkg.go.dev/sql#Result type Result = sql.Result++// Row is a tyny wrapper for https://pkg.go.dev/sql#Row type Row = sql.Row++// Rows is a tyny wrapper for https://pkg.go.dev/sql#Rows type Rows = sql.Rows++// Scanner is a tyny wrapper for https://pkg.go.dev/sql#Scanner type Scanner = sql.Scanner++// Stmt is a tyny wrapper for https://pkg.go.dev/sql#Stmt type Stmt = sql.Stmt++// TxOptions is a tyny wrapper for https://pkg.go.dev/sql#TxOptions type TxOptions = sql.TxOptions // Conn behaves as the standard SQL package one, with the exception that it does not implement the `Raw` method for security reasons.+// Please see https://pkg.go.dev/sql#Conn type Conn struct { c *sql.Conn } -/*-func (c *Conn) Raw(f func(driverConn interface{}) error) (err error) {- // Dangerous to expose-}-*/+// Begin is a tyny wrapper for https://pkg.go.dev/sql#Conn.Begin func (c Conn) BeginTx(ctx context.Context, opts *TxOptions) (Tx, error) { t, err := c.c.BeginTx(ctx, opts) return Tx{t}, err }-func (c Conn) Close() error { return c.c.Close() }++// Close is a tyny wrapper for https://pkg.go.dev/sql#Conn.Close+func (c Conn) Close() error {+ return c.c.Close()+}++// ExecContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.ExecContext func (c Conn) ExecContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (Result, error) { return c.c.ExecContext(ctx, query.s, args) }-func (c Conn) PingContext(ctx context.Context) error { return c.c.PingContext(ctx) }++// PingContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.PingContext+func (c Conn) PingContext(ctx context.Context) error {+ return c.c.PingContext(ctx)+}++// PrepareContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.PrepareContext func (c Conn) PrepareContext(ctx context.Context, query TrustedSQLString) (*Stmt, error) { return c.c.PrepareContext(ctx, query.s) }++// QueryContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.QueryContext func (c Conn) QueryContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (*Rows, error) { return c.c.QueryContext(ctx, query.s, args) }++// QueryRowContext is a tyny wrapper for https://pkg.go.dev/sql#Conn.QueryRowContext func (c Conn) QueryRowContext(ctx context.Context, query TrustedSQLString, args ...interface{}) *Row { return c.c.QueryRowContext(ctx, query.s, args) } // DB behaves as the standard SQL package one, with the exception that it does not implement the `Driver` method for security reasons.+// Please see https://pkg.go.dev/sql#DB type DB struct { db *sql.DB } +// Open is a tyny wrapper for https://pkg.go.dev/sql#Open func Open(driverName, dataSourceName string) (DB, error) { db, err := sql.Open(driverName, dataSourceName) return DB{db}, err }++// OpenDB is a tyny wrapper for https://pkg.go.dev/sql#OpenDB func OpenDB(c driver.Connector) DB { return DB{sql.OpenDB(c)} }++// Begin is a tyny wrapper for https://pkg.go.dev/sql#DB.Begin func (db DB) Begin() (Tx, error) { t, err := db.db.Begin() return Tx{t}, err }++// BeginTx is a tyny wrapper for https://pkg.go.dev/sql#DB.BeginTx func (db DB) BeginTx(ctx context.Context, opts *TxOptions) (Tx, error) { t, err := db.db.BeginTx(ctx, opts) return Tx{t}, err }-func (db DB) Close() error { return db.db.Close() }++// Close is a tyny wrapper for https://pkg.go.dev/sql#DB.Close+func (db DB) Close() error {+ return db.db.Close()+}++// Conn is a tyny wrapper for https://pkg.go.dev/sql#DB.Conn func (db DB) Conn(ctx context.Context) (Conn, error) { c, err := db.db.Conn(ctx) return Conn{c}, err }++// Exec is a tyny wrapper for https://pkg.go.dev/sql#DB.Exec func (db DB) Exec(query TrustedSQLString, args ...interface{}) (Result, error) { return db.db.Exec(query.s, args) }++// ExecContext is a tyny wrapper for https://pkg.go.dev/sql#DB.ExecContext func (db DB) ExecContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (Result, error) { return db.db.ExecContext(ctx, query.s, args) }-func (db DB) Ping() error { return db.db.Ping() }-func (db DB) PingContext(ctx context.Context) error { return db.db.PingContext(ctx) }-func (db DB) Prepare(query TrustedSQLString) (*Stmt, error) { return db.db.Prepare(query.s) }++// Ping is a tyny wrapper for https://pkg.go.dev/sql#DB.Ping+func (db DB) Ping() error {+ return db.db.Ping()+}++// PingContext is a tyny wrapper for https://pkg.go.dev/sql#DB.PingContext+func (db DB) PingContext(ctx context.Context) error {+ return db.db.PingContext(ctx)+}++// Prepare is a tyny wrapper for https://pkg.go.dev/sql#DB.Prepare+func (db DB) Prepare(query TrustedSQLString) (*Stmt, error) {+ return db.db.Prepare(query.s)+}++// PrepareContext is a tyny wrapper for https://pkg.go.dev/sql#DB.PrepareContext func (db DB) PrepareContext(ctx context.Context, query TrustedSQLString) (*Stmt, error) { return db.db.PrepareContext(ctx, query.s) }++// Query is a tyny wrapper for https://pkg.go.dev/sql#DB.Query func (db DB) Query(query TrustedSQLString, args ...interface{}) (*Rows, error) { return db.db.Query(query.s, args) }++// QueryContext is a tyny wrapper for https://pkg.go.dev/sql#DB.QueryContext func (db DB) QueryContext(ctx context.Context, query TrustedSQLString, args ...interface{}) (*Rows, error) { return db.db.QueryContext(ctx, query.s, args) }++// QueryRow is a tyny wrapper for https://pkg.go.dev/sql#DB.QueryRow func (db DB) QueryRow(query TrustedSQLString, args ...interface{}) *Row { return db.db.QueryRow(query.s, args) }++// QueryRowContext is a tyny wrapper for https://pkg.go.dev/sql#DB.QueryRowContext func (db DB) QueryRowContext(ctx context.Context, query TrustedSQLString, args ...interface{}) *Row { return db.db.QueryRowContext(ctx, query.s, args) } -// Go 1.15 func (db DB) SetConnMaxIdleTime(d time.Duration) { db.db.SetConnMaxIdleTime(d) }-func (db DB) SetConnMaxLifetime(d time.Duration) { db.db.SetConnMaxLifetime(d) }-func (db DB) SetMaxIdleConns(n int) { db.db.SetMaxIdleConns(n) }-func (db DB) SetMaxOpenConns(n int) { db.db.SetMaxOpenConns(n) }-func (db DB) Stats() DBStats { return db.db.Stats() }+// Only available in Go 1.15++// func (db DB) SetConnMaxIdleTime(d time.Duration) { db.db.SetConnMaxIdleTime(d) }
why is this commented out?
comment created time in 9 days
push eventgoogle/go-safeweb
commit sha c0ab7601731294e712c7e337fdabd5de9db23522
Correct mistake in SeveMuxConfig godoc
commit sha 8cf19474df3c247a9fd1e2f0f1d31a0b89d9bd40
Change variable names in htmlinject to conform to linters
commit sha 884ebb545cc1acc1cfca4227a3151e2994b638d5
Implemented SetStatus that allows setting a custom response status
commit sha 30fe423a0c92b3cefd359c2b83b68ecd57856326
Document the case in which NoContent, Redirect or WriteError are called after SetCode
commit sha aeb4152afad1925a63b6b937dbee9bf9f1cb86e2
Modify SetCode to not panic if it's called after the response was already written
push time in 10 days
push eventgoogle/go-safeweb
commit sha 8d59fe494209ffab6174b5132e0e9a208012d513
Modify SetCode to not panic if it's called after the response was already written
push time in 10 days
push eventgoogle/go-safeweb
commit sha 0c5a5ebec8c75d03ca3c95d66a0f10a641d95167
Refactored the XSRF plugin to only validate the token in Before and generate the protection in the Commit stage
push time in 10 days
PR opened google/go-safeweb
Moved the functionality that generates protection in the Commit stage so the Before stage only retrieves and validates the XSRF token in case of state-changing requests. This way we don't need to add the token to the safehttp.IncomingRequest context anymore.
pr created time in 10 days
push eventgoogle/go-safeweb
commit sha f6479e6a0aa46f2d279df396f526b874b291ea49
Refactored the XSRF plugin to only validate the token in Before and generate the protection in the Commit stage
push time in 10 days
create barnchgoogle/go-safeweb
branch : maramihali-htmlinject-var-names
created branch time in 10 days
push eventgoogle/go-safeweb
commit sha 87c6f9dfaf518fb976f39cd97a509b63f499d86d
Document the case in which NoContent, Redirect or WriteError are called after SetCode
push time in 11 days
pull request commentgoogle/go-safeweb
Allow the user to set a custom status code in the safehttp.ResponseWriter
Right now it is not treated as a bug and the behavior is the one described by you (btw I should write tests to exemplify this, thanks for pointing it out).
comment created time in 11 days
pull request commentgoogle/go-safeweb
Allow the user to set a custom status code in the safehttp.ResponseWriter
Currently, whatever ResponseWriter.code was set to by calling SetCode would be ignored and the response status will be set properly. That situation would be a bug, but should we actually error/panic in that case? It might be a bit extreme to panic, I don't know whether returning a 500 would be better than ignoring. :-?
comment created time in 11 days
issue openedgoogle/go-safeweb
ResponseWriter.SetCode allows setting a 3XX or 4XX-5XX status code
Ideally, this should only be allowed by calling the Redirect and WriteError methods so we need to decide what should happen if a 3XX-5XX status code is passed to the StatusCode (ideally, not panic).
created time in 11 days
PR opened google/go-safeweb
Fixes #151
Previously, there was no way of setting a status code other than safehttp.NoContent as the write method in the safehttp.ResponseWriter would always set the status code to safehttp.StatusOK. Implemented initial functionality and tests to make this possible.
pr created time in 11 days
PR closed google/go-safeweb
Fixes #151 and depends on PR #205
Allow the user to set a custom status code when writing the response (1XX-2XX) as the safehttp.ResponseWriter.Write method will always set the status code to safehttp.StatusOK.
pr closed time in 11 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfhtml++import (+ "context"+ "crypto/rand"+ "encoding/base64"+ "errors"+ "fmt"+ "github.com/google/go-safeweb/safehttp/plugins/xsrf"++ "github.com/google/go-safeweb/safehttp"+ "golang.org/x/net/xsrftoken"+)++const (+ // TokenKey is the form key used when sending the token as part of POST+ // request.+ TokenKey = "xsrf-token"+ cookieIDKey = "xsrf-cookie"+)++// Interceptor implements XSRF protection.+type Interceptor struct {+ // SecretAppKey uniquely identifies each registered service and should have+ // high entropy as it is used for generating the XSRF token.+ SecretAppKey string+}++var _ safehttp.Interceptor = &Interceptor{}++type tokenCtxKey struct{}++// Token extracts the XSRF token from the incoming request. If it is not+// present, it returns a non-nil error.+func Token(r *safehttp.IncomingRequest) (string, error) {+ tok := r.Context().Value(tokenCtxKey{})+ if tok == nil {+ return "", errors.New("xsrf token not found")+ }+ return tok.(string), nil+}++func addCookieID(w *safehttp.ResponseWriter) (*safehttp.Cookie, error) {+ buf := make([]byte, 20)+ if _, err := rand.Read(buf); err != nil {+ return nil, fmt.Errorf("crypto/rand.Read: %v", err)+ }++ c := safehttp.NewCookie(cookieIDKey, base64.StdEncoding.EncodeToString(buf))+ c.SetSameSite(safehttp.SameSiteStrictMode)+ if err := w.SetCookie(c); err != nil {+ return nil, err+ }+ return c, nil+}++// Before should be executed before directing the safehttp.IncomingRequest to+// the handler to ensure it is not part of a Cross-Site Request+// Forgery attack.+//+// On first user visit through a state preserving request (GET, HEAD or+// OPTIONS), a nonce-based cookie will be set in the response as a way to+// distinguish between users and prevent pre-login XSRF attacks. The cookie will+// be used in the token generation and verification algorithm and is expected to+// be present in all subsequent incoming requests.+//+// For every authorized request, the interceptor will also generate a+// cryptographically-safe XSRF token using the appKey, the cookie and the path+// visited. This can be later extracted using Token and should be injected as a+// hidden input field in HTML forms.+//+// In case of state changing requests (all except GET, HEAD and OPTIONS), the+// interceptor checks for the presence of the XSRF token in the request body+// (expected to have been injected) and validates it.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+ needsValidation := !xsrf.StatePreserving(r)
Since this PR focuses on the Angular XSRF plugin, I will do it in a follow-up PR. Otherwise, I think it will make this PR harder to review since it will have some unrelated changes. Is that okay?
comment created time in 11 days
push eventgoogle/go-safeweb
commit sha 9d83014cb182c124e450e1398121d2363c5050b0
Change the Func names in the CSP and html XSRF plugin with the ones exported by htmlinject
commit sha 8b5c6ab0459e6d60f3f36268daa14f963c1a0f28
Small documentation refactoring according to code review
push time in 11 days
push eventgoogle/go-safeweb
commit sha 6f296983b02a3bcc21a22962875c4b580ec221f7
Simplify test data creation
push time in 14 days
push eventgoogle/go-safeweb
commit sha ca34450af30894d7f8f05f1809e72adb2a53e88c
Simply test data creation
push time in 14 days
pull request commentgoogle/go-safeweb
Added a WriteCode method to the safehttp.ResponseWriter
Up to date, can be merged.
comment created time in 14 days
push eventgoogle/go-safeweb
commit sha a1a1449db11feafecc8ec6627f32de088a78be10
Improve test coverage
push time in 14 days
push eventgoogle/go-safeweb
commit sha a2d86a0595a5a79949684a076f66371654fd30fd
Refactor documentation and variable names according to code review
push time in 14 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+ "crypto/rand"+ "encoding/base64"+ "fmt"+ "github.com/google/go-safeweb/safehttp"+ "github.com/google/go-safeweb/safehttp/plugins/xsrf"+ "strconv"+ "time"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+ // TokenCookieName is the name of the seesion cookie that holds the XSRF+ // token.+ TokenCookieName string+ // TokenHeaderName is the name of the HTTP header that holds the XSRF token.+ TokenHeaderName string+}++var _ safehttp.Interceptor = &Interceptor{}++// Default creates an Interceptor with TokenCookieName set to XSRF-TOKEN and+// TokenHeaderName set to X-XSRF-TOKEN, their default values. However, in order+// to prevent collisions when multiple applications share the same domain or+// subdomain, each application should set a unique name for the cookie.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+func Default() *Interceptor {+ return &Interceptor{+ TokenCookieName: "XSRF-TOKEN",+ TokenHeaderName: "X-XSRF-TOKEN",+ }+}++// Before checks for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+ if xsrf.StatePreserving(r) {+ return safehttp.NotWritten()+ }++ c, err := r.Cookie(it.TokenCookieName)+ if err != nil || c.Value() == "" {+ return w.WriteError(safehttp.StatusForbidden)+ }++ tok := r.Header.Get(it.TokenHeaderName)+ if tok == "" || tok != c.Value() {+ // Only JavaScript running on the user domain can read the+ // cookie and correctly set the token header. Hence, if the same token+ // is found in both the cookie and the header, this guarantees the+ // request came from the user's domain.+ return w.WriteError(safehttp.StatusUnauthorized)+ }++ return safehttp.NotWritten()+}++func (it *Interceptor) addTokenCookie(w *safehttp.ResponseWriter) error {+ tok := make([]byte, 20)+ if _, err := rand.Read(tok); err != nil {+ return fmt.Errorf("crypto/rand.Read: %v", err)+ }+ c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok))++ c.SetSameSite(safehttp.SameSiteStrictMode)+ c.SetPath("/")+ d := 24 * time.Hour+ timeout, err := strconv.Atoi(fmt.Sprintf("%.0f", d.Seconds()))+ if err != nil {+ return err+ }+ // Set the duration of the token cookie to 24 hours.+ c.SetMaxAge(timeout)+ // Needed in order to make the cookie accessible by JavaScript+ // running on the user's domain.+ c.DisableHTTPOnly()++ if err := w.SetCookie(c); err != nil {+ return err+ }+ return nil+}++// Commit generates a cryptographically secure random cookie on the first state+// preserving request (GET, HEAD or OPTION) and sets it in the response. On+// every subsequent request the cookie is expected alongside a header that+// matches its value.+func (it *Interceptor) Commit(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, resp safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+ if c, err := r.Cookie(it.TokenCookieName); err == nil && c.Value() != "" {+ return safehttp.NotWritten()+ }++ if !xsrf.StatePreserving(r) {
Sorry about this, thanks for the suggestions!
comment created time in 14 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+ "crypto/rand"+ "encoding/base64"+ "fmt"+ "github.com/google/go-safeweb/safehttp"+ "github.com/google/go-safeweb/safehttp/plugins/xsrf"+ "strconv"+ "time"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+ // TokenCookieName is the name of the seesion cookie that holds the XSRF+ // token.+ TokenCookieName string+ // TokenHeaderName is the name of the HTTP header that holds the XSRF token.+ TokenHeaderName string+}++var _ safehttp.Interceptor = &Interceptor{}++// Default creates an Interceptor with TokenCookieName set to XSRF-TOKEN and+// TokenHeaderName set to X-XSRF-TOKEN, their default values. However, in order+// to prevent collisions when multiple applications share the same domain or+// subdomain, each application should set a unique name for the cookie.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+func Default() *Interceptor {+ return &Interceptor{+ TokenCookieName: "XSRF-TOKEN",+ TokenHeaderName: "X-XSRF-TOKEN",+ }+}++// Before checks for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+ if xsrf.StatePreserving(r) {+ return safehttp.NotWritten()+ }++ c, err := r.Cookie(it.TokenCookieName)+ if err != nil || c.Value() == "" {+ return w.WriteError(safehttp.StatusForbidden)+ }++ tok := r.Header.Get(it.TokenHeaderName)+ if tok == "" || tok != c.Value() {+ // Only JavaScript running on the user domain can read the+ // cookie and correctly set the token header. Hence, if the same token+ // is found in both the cookie and the header, this guarantees the+ // request came from the user's domain.+ return w.WriteError(safehttp.StatusUnauthorized)+ }++ return safehttp.NotWritten()+}++func (it *Interceptor) addTokenCookie(w *safehttp.ResponseWriter) error {+ tok := make([]byte, 20)+ if _, err := rand.Read(tok); err != nil {+ return fmt.Errorf("crypto/rand.Read: %v", err)+ }+ c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok))++ c.SetSameSite(safehttp.SameSiteStrictMode)+ c.SetPath("/")+ d := 24 * time.Hour+ timeout, err := strconv.Atoi(fmt.Sprintf("%.0f", d.Seconds()))+ if err != nil {+ return err+ }+ // Set the duration of the token cookie to 24 hours.+ c.SetMaxAge(timeout)
I read that it's, in theory, more precise than converting directly to int but I guess it doesn't really matter in this case and it just complicates things.
comment created time in 14 days
push eventgoogle/go-safeweb
commit sha d6a8cd5d4b3127768bfedf54c73f2c0d1ffaebde
Refactored the safehttp.ResponseWriter and safehttp.Dispatcher API to have a single writing method
commit sha c961cc31ffb20f0543c8b656702ffbb972629517
Added helpers for creating TemplateResponse and JSONResponse objects
commit sha 64d0196835304835adec4335ee6019c3491d5382
Moved the functionality provided by the safehttp.DefaultDispatcher in its write method and refactor tests
commit sha 29f215a57fa5df6ca8d7523be255d7b51c986b2a
Reintroduce the ContentType method to the Dispatcher interface and set the CT in the safehttp.ResponseWriter. Refactor some documentation.
commit sha 0c2935ab77e6ecb6abf004a080680030865bc5d4
Replace NewTemplateResponse and NewJSONResponse with JSONResp and TemplateResp
commit sha d0be54624bd80b0b947ba23276a17c820cc7a366
Not take reference to the Template and Data in the TemplateResponse since it is not actually needed
commit sha 7479c27feb1ac1e2b23a9daa1d5073e2bc27567e
Created safehttp.WriteJSON and safehttp.ExecuteTemplate which are helper functions for creating JSONResponse and TemplateResponse and calling the Write function of safehttp.ResponseWriter with those
commit sha b45994bbd3e83debe276376588e0fa40157cb17b
Slightly refactor the documentation of safehttp.DefaultDispatcher.Write
commit sha 83dc52fc0a08fd04bf2ad6eb45942440bcd57211
Added the safehttp.ResponseWriter.WriteCode method that allows setting a custom status code for the response (other than safehttp.StatusOK which is set by default).
push time in 15 days
push eventgoogle/go-safeweb
commit sha 20375e7959ed8b19284cd078427722befbe8b7fd
Use the time package to specify the timeout for the Angular token cookie
commit sha c0f24b99dcc9bda59c5c23cfe31e2265b510f822
Refactor documentation and variable names according to code review
push time in 15 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+ "crypto/rand"+ "encoding/base64"+ "fmt"+ "github.com/google/go-safeweb/safehttp"+ "github.com/google/go-safeweb/safehttp/plugins/xsrf"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+ // TokenCookieName is the name of the seesion cookie that holds the XSRF+ // token.+ TokenCookieName string+ // TokenHeaderName is the name of the HTTP header that holds the XSRF token.+ TokenHeaderName string+}++var _ safehttp.Interceptor = &Interceptor{}++// Default creates an Interceptor with TokenCookieName set to XSRF-TOKEN and+// TokenHeaderName set to X-XSRF-TOKEN, their default values. However, in order+// to prevent collisions when multiple applications share the same domain or+// subdomain, each application should set a unique name for the cookie.+func Default() *Interceptor {+ return &Interceptor{+ TokenCookieName: "XSRF-TOKEN",+ TokenHeaderName: "X-XSRF-TOKEN",+ }+}++// Before checks for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+ if xsrf.StatePreserving(r) {+ return safehttp.NotWritten()+ }++ c, err := r.Cookie(it.TokenCookieName)+ if err != nil || c.Value() == "" {+ return w.WriteError(safehttp.StatusForbidden)+ }++ tok := r.Header.Get(it.TokenHeaderName)+ if tok == "" || tok != c.Value() {+ // Only JavaScript running on the user domain can read the
user of the framework domain, should I rephrase it?
comment created time in 15 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC
I used git add for all the files. In the case of tests, git detected it's a move but for this one it didn't. This is probably because I moved the part that verifies a request's method is state preserving in plugins/xsrf/xsrf.go so, since there were slight modifications in the file, it interpreted it as a new file. Sorry about this, will use git mv in the future.
comment created time in 15 days
PR opened google/go-safeweb
Fixes #151 and depends on PR #205
Allow the user to set a custom status code when writing the response (1XX-2XX) as the safehttp.ResponseWriter.Write method will always set the status code to safehttp.StatusOK.
pr created time in 15 days
create barnchgoogle/go-safeweb
branch : maramihali-write-with-status-code
created branch time in 15 days
push eventgoogle/go-safeweb
commit sha 57c8424c6d21b5acd6b965aafe8a350091757f4d
Slightly refactor the documentation of safehttp.DefaultDispatcher.Write
push time in 15 days
PR closed google/go-safeweb
Fixes #151
Previously, there was no way of setting a status code other than safehttp.NoContent as the write methods in the safehttp.ResponseWriter would always set the status code to safehttp.StatusOK. Implemented initial functionality and tests to make this possible.
pr closed time in 15 days
pull request commentgoogle/go-safeweb
Allow the user to set a custom 1xx or 2xx status code in the safehttp.ResponseWriter
Closing as this was an experimental PR
comment created time in 15 days
push eventgoogle/go-safeweb
commit sha 23e5f90e4e4b0c020a3b88be5aa01eedeb0be93f
Ensure the XSRF plugins satisfy the safehttp.Interceptor interface by adding the OnError methods
commit sha 357bd67520fb35daa18209648982196922543ab4
Change the directory tree to have both XSRF plugins inside the same one and created file that contains helper functions used by both. Refactored some logic according to code review
commit sha c399974c31df7e70ad1b0ae9709333c1479d6aa0
Refactor documentation and added a Default function that sets the TokenCookieName and TokenHeaderName to their default values as indicated by the documentation
push time in 15 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+ "crypto/rand"+ "encoding/base64"+ "fmt"+ "github.com/google/go-safeweb/safehttp"+ "github.com/google/go-safeweb/safehttp/plugins/xsrf"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+ // TokenCookieName is the name of the seesion cookie that holds the XSRF+ // token. In order to prevent collisions when multiple applications share+ // the same domain or subdomain, each application should set a unique name+ // for the cookie.+ TokenCookieName string+ // TokenHeaderName is the name of the HTTP header that holds the XSRF token.+ TokenHeaderName string+}++var _ safehttp.Interceptor = &Interceptor{}++// Before checks for the presence of a matching XSRF token, generated on the+// first page access, in both a cookie and a header. Their names should be set+// when the Interceptor is created.+func (it *Interceptor) Before(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+ if xsrf.StatePreserving(r) {+ return safehttp.NotWritten()+ }++ c, err := r.Cookie(it.TokenCookieName)+ if err != nil || c.Value() == "" {+ return w.WriteError(safehttp.StatusForbidden)+ }++ tok := r.Header.Get(it.TokenHeaderName)+ if tok == "" || tok != c.Value() {+ return w.WriteError(safehttp.StatusUnauthorized)+ }++ return safehttp.NotWritten()+}++func (it *Interceptor) addAngularTokenCookie(w *safehttp.ResponseWriter) error {+ tok := make([]byte, 20)+ if _, err := rand.Read(tok); err != nil {+ return fmt.Errorf("crypto/rand.Read: %v", err)+ }+ c := safehttp.NewCookie(it.TokenCookieName, base64.StdEncoding.EncodeToString(tok))++ c.SetSameSite(safehttp.SameSiteStrictMode)+ c.SetPath("/")+ // Set the duration of the token cookie to 24 hours.+ c.SetMaxAge(86400)
time.Hour is an int64 and returns the number of nanoseconds while the MaxAge field of the http.Cookie, which is wrapped by safehttp.Cookie, expects the number of seconds as an int (which made me not use the time package in the first place).
comment created time in 15 days
pull request commentgoogle/go-safeweb
It seemed that it correctly moved everything besides one file (because we kept safehttp/plugins/xsrf/xsrf.go for functionality shared by both versions).
comment created time in 15 days
Pull request review commentgoogle/go-safeweb
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++package xsrfangular++import (+ "crypto/rand"+ "encoding/base64"+ "fmt"+ "github.com/google/go-safeweb/safehttp"+ "github.com/google/go-safeweb/safehttp/plugins/xsrf"+)++// Interceptor provides protection against Cross-Site Request Forgery attacks+// for Angular's XHR requests.+//+// See https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection for more details.+type Interceptor struct {+ // TokenCookieName is the name of the seesion cookie that holds the XSRF
Created a Default() function that instantiates the Interceptor with its default values.
comment created time in 15 days
push eventgoogle/go-safeweb
commit sha 7997723d27f7adf2c17e40ac02bad542cbd99715
coop: internal refactor
commit sha 99fac467e2ff942ba24dae45499d79fcb1592044
Extended the Interceptor interface with the OnError method that will run when an error occurs
commit sha 21a1a8a0e1b2c81cdf2ff24e8a751cc55813eb22
Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called
commit sha 0fdaf2caa7673b45f9a247b46bd16c7aae6c76a4
Fix typo
commit sha 51efe94e782a30555cad7015be7740c5ae08d401
Add the OnError method to the COOP plugin
commit sha 916a81796476aac5af50a93edfdd0c82d5612644
Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called
commit sha 5f2eff00b0086b7c3a9cb017c2d828b455e054b2
Refactor the tests that use the recover function to check for != nil i.e. recover actually recovered from a panic
commit sha 3207156a35658051b60cea53d2ae83357e926107
Refactored the safehttp.ResponseWriter and safehttp.Dispatcher API to have a single writing method
commit sha 80111ebefb4ebf30f87547527c717fbfe185b0d4
Added helpers for creating TemplateResponse and JSONResponse objects
commit sha b4f9feca009c0ba31ed2f13ee46add54532b0183
Moved the functionality provided by the safehttp.DefaultDispatcher in its write method and refactor tests
commit sha 4be161316b0d808acad642fb476b135cd0e8c6e3
Reintroduce the ContentType method to the Dispatcher interface and set the CT in the safehttp.ResponseWriter. Refactor some documentation.
commit sha 71397a76b6c4d5824b30077ea9ba9a492f9b53f5
Replace NewTemplateResponse and NewJSONResponse with JSONResp and TemplateResp
commit sha b51d1179280b8b7cc5bb9d61c9baef0a5a3c1d7d
Not take reference to the Template and Data in the TemplateResponse since it is not actually needed
commit sha c931758534c6bc2adf45815edad9358194a89571
Created safehttp.WriteJSON and safehttp.ExecuteTemplate which are helper functions for creating JSONResponse and TemplateResponse and calling the Write function of safehttp.ResponseWriter with those
push time in 15 days
Pull request review commentgoogle/go-safeweb
ResponseWriter and Dispatcher now have a single Write method
type Template interface { // TemplateResponse bundles a Template with its data and names to function // mappings to be passed together to the commit phase. type TemplateResponse struct {- Template *Template- Data *interface{}+ Template Template+ Data interface{} FuncMap map[string]interface{} } // NoContentResponse is sent to the commit phase when it's initiated from // ResponseWriter.NoContent. type NoContentResponse struct{}++// JSONResp creates a JSONResponse from a data object. This should be a+// valid JSON object that can be serialised and written to the+// http.ResponseWriter using a JSON encoder.+func JSONResp(data interface{}) JSONResponse {+ return JSONResponse{Data: data}+}++// TemplateResp creates a TemplateResponse from a Template, its data and+// the name to function mappings. These will be pre-processed and then written+// to the http.ResponseWriter, if deemed safe. If the funcMap is non-nil, its+// elements will override the existing name to function mappings in the+// template. An attempt to define a new name to function mapping that is not+// already in the template will result in a panic.+func TemplateResp(t Template, data interface{}, funcMap map[string]interface{}) TemplateResponse {+ return TemplateResponse{+ Template: t,+ Data: data,+ FuncMap: funcMap,+ }+}
I think it's not a big change to add the two high-level functions.
comment created time in 15 days
Pull request review commentgoogle/go-safeweb
ResponseWriter and Dispatcher now have a single Write method
func TestResponseWriterWriteTwicePanic(t *testing.T) { }, }, {- name: "Call WriteJSON twice",+ name: "Call NoContent twice", write: func(w *safehttp.ResponseWriter) {- obj := struct{ Field string }{Field: "myField"}- w.WriteJSON(obj)- w.WriteJSON(obj)+ w.NoContent()+ w.NoContent() }, }, {- name: "Call Write then WriteJSON",+ name: "Call WriteError twice", write: func(w *safehttp.ResponseWriter) {- obj := struct{ Field string }{Field: "myField"}- w.Write(obj)- w.WriteJSON(obj)+ w.WriteError(safehttp.StatusInternalServerError)+ w.WriteError(safehttp.StatusInternalServerError) }, }, {- name: "Call WriteTemplate twice",+ name: "Call Redirect twice", write: func(w *safehttp.ResponseWriter) {- w.WriteTemplate(template.Must(template.New("name").Parse("<h1>{{ . }}</h1>")), "This is an actual heading, though.")- w.WriteTemplate(template.Must(template.New("name").Parse("<h1>{{ . }}</h1>")), "This is an actual heading, though.")+ ir := safehttptest.NewRequest(safehttp.MethodGet, "/", nil)+ w.Redirect(ir, "/asdf", safehttp.StatusMovedPermanently)+ w.Redirect(ir, "/asdf", safehttp.StatusMovedPermanently) }, },+ }++ for _, tt := range tests {+ t.Run(tt.name, func(t *testing.T) {+ w := safehttp.NewResponseWriter(safehttp.DefaultDispatcher{}, safehttptest.NewTestResponseWriter(&strings.Builder{}), nil)+ defer func() {+ if r := recover(); r == nil {
Thanks for pointing this out. :) Got used to checking whether recover() returns nil instead of the other way around.
comment created time in 15 days
push eventgoogle/go-safeweb
commit sha 7997723d27f7adf2c17e40ac02bad542cbd99715
coop: internal refactor
commit sha 99fac467e2ff942ba24dae45499d79fcb1592044
Extended the Interceptor interface with the OnError method that will run when an error occurs
commit sha 21a1a8a0e1b2c81cdf2ff24e8a751cc55813eb22
Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called
commit sha 0fdaf2caa7673b45f9a247b46bd16c7aae6c76a4
Fix typo
commit sha 51efe94e782a30555cad7015be7740c5ae08d401
Add the OnError method to the COOP plugin
commit sha 916a81796476aac5af50a93edfdd0c82d5612644
Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called
commit sha 5f2eff00b0086b7c3a9cb017c2d828b455e054b2
Refactor the tests that use the recover function to check for != nil i.e. recover actually recovered from a panic
commit sha 8a1775b20c56a3f5dac48665c153efcbd8fd3e01
Implemented support for Angular's XHR XSRF protection
commit sha bd267fc9178c22edc5750fdf581d4dc8c947abc6
Change the directory tree to have both XSRF plugins inside the same one and created file that contains helper functions used by both. Refactored some logic according to code review.
commit sha 8eb6872d3609823762a6469b851439f6b46d4805
Ensure the XSRF plugins satisfy the safehttp.Interceptor interface by adding the OnError methods
push time in 16 days
push eventgoogle/go-safeweb
commit sha d04794fa617dd2335ce1779b17542143e72b1b20
Change the directory tree to have both XSRF plugins inside the same one and created file that contains helper functions used by both. Refactored some logic according to code review.
push time in 16 days
Pull request review commentgoogle/go-safeweb
Created a XSRF plugin for Angular
+// Copyright 2020 Google LLC+//+// Licensed under the Apache License, Version 2.0 (the "License");+// you may not use this file except in compliance with the License.+// You may obtain a copy of the License at+//+// https://www.apache.org/licenses/LICENSE-2.0+//+// Unless required by applicable law or agreed to in writing, software+// distributed under the License is distributed on an "AS IS" BASIS,+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+// See the License for the specific language governing permissions and+// limitations under the License.++// Package xsrf provides a safehttp.Interceptor that ensures Cross-Site Request+// Forgery protection by verifying the incoming requests, rejecting those+// requests that are suspected to be part of an attack.+package angularxsrf++import (+ "github.com/google/go-cmp/cmp"+ "github.com/google/go-safeweb/safehttp"+ "github.com/google/go-safeweb/safehttp/safehttptest"+ "strings"+ "testing"+)++const (+ cookieName = "XSRF-TOKEN"+ headerName = "X-XSRF-TOKEN"+)++func TestAngularAddCookie(t *testing.T) {+ req := safehttptest.NewRequest(safehttp.MethodGet, "/", nil)+ rec := safehttptest.NewResponseRecorder()+ i := &Interceptor{+ TokenCookieName: cookieName,+ TokenHeaderName: headerName,+ }+ i.Commit(rec.ResponseWriter, req, nil, nil)++ if got, want := rec.Status(), safehttp.StatusOK; got != want {+ t.Errorf("rec.Status(): got %v, want %v", got, want)+ }++ tokCookieDefaults := "Path=/; Max-Age=86400; Secure; SameSite=Strict"+ got := map[string][]string(rec.Header())["Set-Cookie"][0]+ if !strings.Contains(got, i.TokenCookieName) {+ t.Errorf("Set-Cookie header: %s not present", i.TokenCookieName)+ }+ if !strings.Contains(got, tokCookieDefaults) {+ t.Errorf("Set-Cookie header: got %s, want defaults %s", got, tokCookieDefaults)+ }++ if got, want := rec.Body(), ""; got != want {+ t.Errorf("rec.Body(): got %q want %q", got, want)+ }+}++func TestAngularPostProtection(t *testing.T) {+ tests := []struct {+ name string+ req *safehttp.IncomingRequest+ wantStatus safehttp.StatusCode+ wantHeader map[string][]string+ wantBody string+ }{+ {+ name: "Same cookie and header",+ req: func() *safehttp.IncomingRequest {+ req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+ req.Header.Set("Cookie", cookieName+"="+"1234")+ req.Header.Set(headerName, "1234")+ return req+ }(),+ wantStatus: safehttp.StatusOK,+ wantHeader: map[string][]string{},+ wantBody: "",+ },+ {+ name: "Different cookie and header",+ req: func() *safehttp.IncomingRequest {+ req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+ req.Header.Set("Cookie", cookieName+"="+"5768")+ req.Header.Set(headerName, "1234")+ return req+ }(),+ wantStatus: safehttp.StatusUnauthorized,+ wantHeader: map[string][]string{+ "Content-Type": {"text/plain; charset=utf-8"},+ "X-Content-Type-Options": {"nosniff"},+ },+ wantBody: "Unauthorized\n",+ },+ {+ name: "Missing header",+ req: func() *safehttp.IncomingRequest {+ req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+ req.Header.Set("Cookie", cookieName+"="+"1234")+ return req+ }(),+ wantStatus: safehttp.StatusUnauthorized,+ wantHeader: map[string][]string{+ "Content-Type": {"text/plain; charset=utf-8"},+ "X-Content-Type-Options": {"nosniff"},+ },+ wantBody: "Unauthorized\n",+ },+ {+ name: "Missing cookie",+ req: func() *safehttp.IncomingRequest {+ req := safehttptest.NewRequest(safehttp.MethodPost, "/", nil)+ req.Header.Set(headerName, "1234")+ return req+ }(),+ wantStatus: safehttp.StatusForbidden,+ wantHeader: map[string][]string{+ "Content-Type": {"text/plain; charset=utf-8"},+ "X-Content-Type-Options": {"nosniff"},+ },+ wantBody: "Forbidden\n",
Right, but we tested for the headers everywhere and I mostly did it for uniformity across tests. I suggest removing these checks, if you think they are not needed, in a follow-up PR.
comment created time in 16 days
Pull request review commentgoogle/go-safeweb
ResponseWriter and Dispatcher now have a single Write method
type TemplateResponse struct { // NoContentResponse is sent to the commit phase when it's initiated from // ResponseWriter.NoContent. type NoContentResponse struct{}++// NewJSONResponse creates a JSON response from a data object. This should be a+// valid JSON object that can be serialised and written to the+// http.ResponseWriter using a JSON encoder.+func NewJSONResponse(data interface{}) JSONResponse {+ return JSONResponse{Data: data}+}++// NewTemplateResponse creates a TemplateResponse from a Template, its data and+// the name to function mappings. These will be pre-processed and then written+// to the http.ResponseWriter, if deemed safe. If the funcMap is non-nil, its+// elements will override the existing name to function mappings in the+// template. An attempt to define a new name to function mapping that is not+// already in the template will result in a panic.+func NewTemplateResponse(t Template, data interface{}, funcMap map[string]interface{}) TemplateResponse {+ return TemplateResponse{+ Template: &t,+ Data: &data,
Right, that is not actually needed. When we designed the commit phase we've assumed the token and nonce injection is going to be done directly by the XSRF and CSP plugin which is not actually the case.
comment created time in 16 days
Pull request review commentgoogle/go-safeweb
ResponseWriter and Dispatcher now have a single Write method
func TestDefaultDispatcherValidResponse(t *testing.T) { Parse("<h1>{{ . }}</h1>"))) var data interface{} data = "This is an actual heading, though."- resp := safehttp.TemplateResponse{- Template: &t,- Data: &data,- }- return d.ExecuteTemplate(w, resp)+ return d.Write(w, safehttp.NewTemplateResponse(t, data, nil))
Is TemplateResp and JSONResp okay?
comment created time in 16 days
push eventgoogle/go-safeweb
commit sha 01d472f4d6007df0a09d9a1855fe3dd36d885106
Replace NewTemplateResponse and NewJSONResponse with JSONResp and TemplateResp
commit sha 81fe5c8dc7f2ffe9cfd39964662b7e2d53edcf85
Not take reference to the Template and Data in the TemplateResponse since it is not actually needed
push time in 16 days
push eventgoogle/go-safeweb
commit sha 8e99b572b8be7c413784fdd1cbd958951fc4e029
Implemented support for Angular's XHR XSRF protection
push time in 16 days
PR opened google/go-safeweb
Fixes #194
Created a separate plugin that provides protection against XSRF in Angular. More details can be found here: https://docs.angularjs.org/api/ng/service/$http#cross-site-request-forgery-xsrf-protection
pr created time in 16 days
push eventgoogle/go-safeweb
commit sha b9de3626054ca01270c7d3e9c1c45ba9fa6f78ff
Reintroduce the ContentType method to the Dispatcher interface and set the CT in the safehttp.ResponseWriter. Refactor some documentation.
push time in 16 days
PR opened google/go-safeweb
Modified the tests that ensure a panic was triggered from
defer func() {
if r := recover(); r == nil {
t.Errorf(`... expected panic`)
}
}()
to
defer func() {
if r := recover(); r != nil {
return
}
t.Errorf(`... expected panic`)
}()
i.e. they now check the recover() function recovered from a panic instead of checking it didn't.
pr created time in 16 days
pull request commentgoogle/go-safeweb
Added the OnError method to the safehttp.Interceptor interface
Rebased on top of master, ready for merge.
comment created time in 16 days
Pull request review commentgoogle/go-safeweb
Added the OnError method to the safehttp.Interceptor interface
type Interceptor interface { // is written to the ResponseWriter, then the Commit phases from the // remaining interceptors won't execute. Commit(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result++ // OnError runs when ResponseWriter.WriteError is called, before the+ // actual error response is written. An attempt to write to the+ // ResponseWriter in this phase will result in an irrecoverable error.+ OnError(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result
Added a TODO
comment created time in 16 days
push eventgoogle/go-safeweb
commit sha 3cb9a71d37d217fa4f6bdf293cccd917be759c0b
Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called
push time in 16 days
push eventgoogle/go-safeweb
commit sha 6061277419bcb621a649dfa76614dbb8e7a78936
Add the OnError method to the COOP plugin
push time in 17 days
PR closed google/go-safeweb
Fixes #194
Extended the XSRF plugin so it can be configured to provide XSRF protection for Angular. This is achieved by generating a cryptographically random token and assigning it to the XSRF-TOKEN cookie, set in the response to the first GET request. On all subsequent requests, the value of this cookie is expected to match the value of a custom request header, X-XSRF-TOKEN.
pr closed time in 17 days
pull request commentgoogle/go-safeweb
Implemented XSRF protection for Angular
Closed as this will be then as a follow-up PR for #199.
comment created time in 17 days
PR closed google/go-safeweb
Fixes #194
Extended the XSRF plugin so it can be configured to provide XSRF protection for Angular. This is achieved by generating a cryptographically random token and assigning it to the XSRF-TOKEN cookie, set in the response to the first GET request. On all subsequent requests, the value of this cookie is expected to match the value of a custom request header, X-XSRF-TOKEN.
pr closed time in 17 days
push eventgoogle/go-safeweb
commit sha 763283a19bd42e2e953f42f5a9f79dffcfd0b043
Added COOP implementation.
commit sha 99cac8da76106931d50a6df050f05f0e55bf7c9c
Added copyright to COOP plugin.
commit sha 13783527bae3ac0a2250a4c69ca67888e732dc8d
coop: rename some unclear variables, add some docs.
commit sha 514128f3f101da67a388966e579cf449a37304b5
htmlinject: provide helpers to load templates in a safe and ergonomic way
commit sha 140071403cb62c8ad873813b54fa29c677ab5ef3
htmlinject: provide clearer configs
commit sha 150831a41f1c6440f5ffa3a6945169ae5964af0a
htmlinject: address CR comments.
commit sha 1264660868372c3aede30fbbb8e87c87a1fd424b
Extended the Interceptor interface with the OnError method that will run when an error occurs
commit sha 6fb0512158f04d2ed0cbf77de8b7993abe87e490
Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called
commit sha be9220cf8c881ce813009ccfd3c552aff353d864
Fix typo
push time in 17 days
PR opened google/go-safeweb
Fixes #150
Unified the functionality of WriteJSON, ExecuteTemplate and Write inside the new safehttp.DefaultDispatcher.Write method, also responsible for setting the Content-Type header and writing the status code.
pr created time in 17 days
Pull request review commentgoogle/go-safeweb
Added the OnError method to the safehttp.Interceptor interface
func TestMuxHandlerReturnsNotWritten(t *testing.T) { t.Errorf(`response body got: %q want: ""`, got) } }++type onErrorWriteInterceptor struct{}++func (onErrorWriteInterceptor) Before(w *safehttp.ResponseWriter, _ *safehttp.IncomingRequest, _ safehttp.InterceptorConfig) safehttp.Result {+ return w.WriteError(safehttp.StatusBadRequest)+}++func (onErrorWriteInterceptor) Commit(_ *safehttp.ResponseWriter, _ *safehttp.IncomingRequest, _ safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+ return safehttp.Result{}+}++func (onErrorWriteInterceptor) OnError(w *safehttp.ResponseWriter, _ *safehttp.IncomingRequest, _ safehttp.Response, _ safehttp.InterceptorConfig) safehttp.Result {+ w.Header().Set("header", "bad")+ return w.WriteError(safehttp.StatusInsufficientStorage)+}++func TestMuxInterceptorWriteInOnError(t *testing.T) {+ b := &strings.Builder{}+ rw := safehttptest.NewTestResponseWriter(b)++ req := httptest.NewRequest(safehttp.MethodGet, "http://foo.com/bar", nil)++ mb := &safehttp.ServeMuxConfig{}+ mb.Intercept(onErrorWriteInterceptor{})++ registeredHandler := safehttp.HandlerFunc(func(w *safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {+ return w.Write(safehtml.HTMLEscaped("<h1>Hello World!</h1>"))+ })+ mb.Handle("/bar", safehttp.MethodGet, registeredHandler)++ defer func() {+ if r := recover(); r == nil {+ t.Errorf(`mb.Mux().ServeHTTP(rw, req): expected panic`)+ }+ }()
Why is this the case? We have done it this way in all test cases so it will be a bit strange to just change one of them. Should I make a PR that applies the change everywhere?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
Added the OnError method to the safehttp.Interceptor interface
type Interceptor interface { // is written to the ResponseWriter, then the Commit phases from the // remaining interceptors won't execute. Commit(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result++ // OnError runs when ResponseWriter.WriteError is called, before the+ // actual error response is written. An attempt to write to the+ // ResponseWriter in this phase will result in an irrecoverable error.+ OnError(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result
what do you think we could pass? It seems to me that the interceptor should be able to figure that out by itself from the ResponseWriter, IncomingRequest and Response if needed (also the function takes 4 arguments already)
comment created time in 17 days
push eventgoogle/go-safeweb
commit sha efcbea1e8e2677a4db995ec06dcead7aacee1391
Fix typo
push time in 17 days
Pull request review commentgoogle/go-safeweb
func (p Policy) String() string { return string(p.Mode) + `; report-to "` + p.ReportingGroup + `"` } -// NewInterceptor constructs an interceptor that applies the given policies.-func NewInterceptor(policies ...Policy) Interceptor {- var rep []string- var enf []string+type serializedPolicies struct {+ rep []string+ enf []string+}++func serializePolicies(policies ...Policy) serializedPolicies {+ var s serializedPolicies for _, p := range policies { if p.ReportOnly {- rep = append(rep, p.String())+ s.rep = append(s.rep, p.String()) } else {- enf = append(enf, p.String())+ s.enf = append(s.enf, p.String()) } }- return Interceptor{rep: rep, enf: enf}+ return s+}++// NewInterceptor constructs an interceptor that applies the given policies.+func NewInterceptor(policies ...Policy) Interceptor {
Why is New as function name not enough?
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
Added the OnError method to the safehttp.Interceptor interface
func (h handler) commitPhase(w *ResponseWriter, resp Response) { } } }++// errrorPhase calls the OnError phases of all the interceptors associated with+// a handler. This stage runs before an error response is written to the+// ResponseWriter.+//+// TODO: BIG WARNING, if an interceptor attempts to write to the ResponseWriter+// in the OnError phase then this method will recurse.
I changed it to panic if that happens.
comment created time in 17 days
Pull request review commentgoogle/go-safeweb
Added the OnError method to the safehttp.Interceptor interface
type Interceptor interface { // is written to the ResponseWriter, then the Commit phases from the // remaining interceptors won't execute. Commit(w *ResponseWriter, r *IncomingRequest, resp Response, cfg InterceptorConfig) Result++ // OnError runs when ResponseWriter.WriteError is called, before the+ // actual error response is written.
Yep, I thought a bit more about this and I think, in this PR, an attempt to write to the ResponseWriter in OnError should just trigger a panic.
comment created time in 17 days
push eventgoogle/go-safeweb
commit sha 2e61f79792f93bd076a2b7f27badc7412920365a
Extended the Interceptor interface with the OnError method that will run when an error occurs
commit sha 050396953242366eed09e49abae6d9360f9ea80a
Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called
push time in 17 days
push eventgoogle/go-safeweb
commit sha e4e273480803efa97b1935fed401ea8d9e3e2da7
more work
push time in 17 days
Pull request review commentgoogle/go-safeweb
Refactored the XSRF plugin to a more general structure
import ( "crypto/rand" "encoding/base64" "errors"- "fmt"- "github.com/google/go-safeweb/safehttp" "golang.org/x/net/xsrftoken" ) const (- // TokenKey is the form key used when sending the token as part of POST- // request.- TokenKey = "xsrf-token" cookieIDKey = "xsrf-cookie"+ TokenKey = "xsrf-token" ) +type tokenCtxKey struct{}+ var statePreservingMethods = map[string]bool{ safehttp.MethodGet: true, safehttp.MethodHead: true, safehttp.MethodOptions: true, } -// Interceptor implements XSRF protection. type Interceptor struct {- // SecretAppKey uniquely identifies each registered service and should have- // high entropy as it is used for generating the XSRF token.- SecretAppKey string+ secretAppKey string+ g Generator+ c Checker+ i Injector
The Injector should probably take care of ensuring all protection is there (i.e. it should set cookies and/or inject the template with the token, based on the case) so "inject the protection". Currently, generating the token is also done in the Injector. It is rather hard to abstract away the Generator (the thing that should generate the token) from the Injector, as the info it needs depends on whether the plugin provides protection for Angular or for the "default" case as well as what crypto stuff it uses to generate the token. Hence, a Generate function would take different arguments based on the case. One alternative I could think of is the following:
type Generator interface {
Generate(r *safehttp.IncomingRequest, data ...interface{}) (string, error)
}
Then, in the "default case":
- we pass the
secretAppKeyandcookieIDin theInjectfunction alongside the request andGenerategenerates the token either using Tink orxsrftoken
and in the Angular case:
- we could pass a
secretAppKey with the request and the token to be set in the cookie could be generated using that key or simply usingcrypto/rand` (afaiu, pre-login protection is not an issue in Angular, right?)
comment created time in 17 days