profile
viewpoint
maramihali Romania I sometimes code and it's fun. Intern @google

google/go-safeweb 330

Secure-by-default HTTP servers in Go.

hackersatcambridge/hac-website 24

The Hackers at Cambridge Website Repo

maramihali/Attempts_On_Go 0

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

maramihali/C-Tick 0

C Tick as part of Cambridge Computer Science Part IB course that analyses and removes IP and TCP headers from log files

maramihali/DTrace-Tutorials 0

My attempt on scripts presented in the DTrace tutorials from dtrace.org

maramihali/freebsd 0

FreeBSD src tree http://www.FreeBSD.org/

maramihali/go-safeweb 0

Secure-by-default HTTP servers in Go.

maramihali/report-to-parser 0

A simple package to parse reports coming from browsers implementing the Reporting API

issue commentgoogle/go-safeweb

Add a Server type

Another article that provides useful information on timeouts is this one

Thing to keep in mind:

  • we need to set either ReadTimeout (header + body) or ReadHeaderTimeout to prevent server resource exhaustation and DoS attacks
  • the Server's Handler field should never be nil, but set to an instantiated ServeMux as otherwise handlers will be registered on the http.DefaultServeMux which 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.ListenAndServe or http.Serve and could create equivalents with sane defaults
empijei

comment created time in an hour

Pull request review commentgoogle/go-safeweb

safesql: fix doc

 import ( 	"time" ) +// Drivers is a tyny wrapper for https://pkg.go.dev/sql#Drivers

tiny (here and below)

empijei

comment created time in 9 days

Pull request review commentgoogle/go-safeweb

safesql: fix doc

 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?

empijei

comment created time in 9 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventgoogle/go-safeweb

maramihali

commit sha c0ab7601731294e712c7e337fdabd5de9db23522

Correct mistake in SeveMuxConfig godoc

view details

Mara Mihali

commit sha 8cf19474df3c247a9fd1e2f0f1d31a0b89d9bd40

Change variable names in htmlinject to conform to linters

view details

Mara Mihali

commit sha 884ebb545cc1acc1cfca4227a3151e2994b638d5

Implemented SetStatus that allows setting a custom response status

view details

Mara Mihali

commit sha 30fe423a0c92b3cefd359c2b83b68ecd57856326

Document the case in which NoContent, Redirect or WriteError are called after SetCode

view details

Mara Mihali

commit sha aeb4152afad1925a63b6b937dbee9bf9f1cb86e2

Modify SetCode to not panic if it's called after the response was already written

view details

push time in 10 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 8d59fe494209ffab6174b5132e0e9a208012d513

Modify SetCode to not panic if it's called after the response was already written

view details

push time in 10 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 0c5a5ebec8c75d03ca3c95d66a0f10a641d95167

Refactored the XSRF plugin to only validate the token in Before and generate the protection in the Commit stage

view details

push time in 10 days

PR opened google/go-safeweb

Refactored the XSRF plugin that configures proteciton for HTML pages internal cleanup plugin

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.

+201 -209

0 comment

3 changed files

pr created time in 10 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha f6479e6a0aa46f2d279df396f526b874b291ea49

Refactored the XSRF plugin to only validate the token in Before and generate the protection in the Commit stage

view details

push time in 10 days

create barnchgoogle/go-safeweb

branch : maramihali-xsrf-cleanup

created branch time in 10 days

create barnchgoogle/go-safeweb

branch : maramihali-htmlinject-var-names

created branch time in 10 days

PR opened google/go-safeweb

Reviewers
Correct mistake in SeveMuxConfig godoc
+1 -1

0 comment

1 changed file

pr created time in 10 days

create barnchgoogle/go-safeweb

branch : maramihali-mux-godoc

created branch time in 10 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 87c6f9dfaf518fb976f39cd97a509b63f499d86d

Document the case in which NoContent, Redirect or WriteError are called after SetCode

view details

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).

maramihali

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. :-?

maramihali

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

Reviewers
Allow the user to set a custom status code in the safehttp.ResponseWriter hacktoberfest-accepted

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.

+98 -1

0 comment

2 changed files

pr created time in 11 days

create barnchgoogle/go-safeweb

branch : maramihali-status-code

created branch time in 11 days

PR closed google/go-safeweb

Reviewers
Added a WriteCode method to the safehttp.ResponseWriter enhancement hacktoberfest-accepted

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.

+53 -2

1 comment

2 changed files

maramihali

pr closed time in 11 days

Pull request review commentgoogle/go-safeweb

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 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?

maramihali

comment created time in 11 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 9d83014cb182c124e450e1398121d2363c5050b0

Change the Func names in the CSP and html XSRF plugin with the ones exported by htmlinject

view details

Mara Mihali

commit sha 8b5c6ab0459e6d60f3f36268daa14f963c1a0f28

Small documentation refactoring according to code review

view details

push time in 11 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 6f296983b02a3bcc21a22962875c4b580ec221f7

Simplify test data creation

view details

push time in 14 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha ca34450af30894d7f8f05f1809e72adb2a53e88c

Simply test data creation

view details

push time in 14 days

pull request commentgoogle/go-safeweb

Added a WriteCode method to the safehttp.ResponseWriter

Up to date, can be merged.

maramihali

comment created time in 14 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha a1a1449db11feafecc8ec6627f32de088a78be10

Improve test coverage

view details

push time in 14 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha a2d86a0595a5a79949684a076f66371654fd30fd

Refactor documentation and variable names according to code review

view details

push time in 14 days

Pull request review commentgoogle/go-safeweb

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 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!

maramihali

comment created time in 14 days

PullRequestReviewEvent

Pull request review commentgoogle/go-safeweb

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 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.

maramihali

comment created time in 14 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha d6a8cd5d4b3127768bfedf54c73f2c0d1ffaebde

Refactored the safehttp.ResponseWriter and safehttp.Dispatcher API to have a single writing method

view details

Mara Mihali

commit sha c961cc31ffb20f0543c8b656702ffbb972629517

Added helpers for creating TemplateResponse and JSONResponse objects

view details

Mara Mihali

commit sha 64d0196835304835adec4335ee6019c3491d5382

Moved the functionality provided by the safehttp.DefaultDispatcher in its write method and refactor tests

view details

Mara Mihali

commit sha 29f215a57fa5df6ca8d7523be255d7b51c986b2a

Reintroduce the ContentType method to the Dispatcher interface and set the CT in the safehttp.ResponseWriter. Refactor some documentation.

view details

Mara Mihali

commit sha 0c2935ab77e6ecb6abf004a080680030865bc5d4

Replace NewTemplateResponse and NewJSONResponse with JSONResp and TemplateResp

view details

Mara Mihali

commit sha d0be54624bd80b0b947ba23276a17c820cc7a366

Not take reference to the Template and Data in the TemplateResponse since it is not actually needed

view details

Mara Mihali

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

view details

Mara Mihali

commit sha b45994bbd3e83debe276376588e0fa40157cb17b

Slightly refactor the documentation of safehttp.DefaultDispatcher.Write

view details

Mara Mihali

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).

view details

push time in 15 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 20375e7959ed8b19284cd078427722befbe8b7fd

Use the time package to specify the timeout for the Angular token cookie

view details

Mara Mihali

commit sha c0f24b99dcc9bda59c5c23cfe31e2265b510f822

Refactor documentation and variable names according to code review

view details

push time in 15 days

Pull request review commentgoogle/go-safeweb

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 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?

maramihali

comment created time in 15 days

Pull request review commentgoogle/go-safeweb

XSRF plugin for Angular

+// 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.

maramihali

comment created time in 15 days

PullRequestReviewEvent
PullRequestReviewEvent

PR opened google/go-safeweb

Reviewers
Added a WriteCode method to the safehttp.ResponseWriter enhancement hacktoberfest-accepted

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.

+167 -172

0 comment

9 changed files

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

Mara Mihali

commit sha 57c8424c6d21b5acd6b965aafe8a350091757f4d

Slightly refactor the documentation of safehttp.DefaultDispatcher.Write

view details

push time in 15 days

PR closed google/go-safeweb

Reviewers
Allow the user to set a custom 1xx or 2xx status code in the safehttp.ResponseWriter enhancement hacktoberfest-accepted

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.

+168 -7

2 comments

2 changed files

maramihali

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

maramihali

comment created time in 15 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 23e5f90e4e4b0c020a3b88be5aa01eedeb0be93f

Ensure the XSRF plugins satisfy the safehttp.Interceptor interface by adding the OnError methods

view details

Mara Mihali

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

view details

Mara Mihali

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

view details

push time in 15 days

Pull request review commentgoogle/go-safeweb

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 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).

maramihali

comment created time in 15 days

PullRequestReviewEvent

pull request commentgoogle/go-safeweb

XSRF plugin for Angular

It seemed that it correctly moved everything besides one file (because we kept safehttp/plugins/xsrf/xsrf.go for functionality shared by both versions).

maramihali

comment created time in 15 days

Pull request review commentgoogle/go-safeweb

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 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.

maramihali

comment created time in 15 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

empijei

commit sha 7997723d27f7adf2c17e40ac02bad542cbd99715

coop: internal refactor

view details

Mara Mihali

commit sha 99fac467e2ff942ba24dae45499d79fcb1592044

Extended the Interceptor interface with the OnError method that will run when an error occurs

view details

Mara Mihali

commit sha 21a1a8a0e1b2c81cdf2ff24e8a751cc55813eb22

Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called

view details

Mara Mihali

commit sha 0fdaf2caa7673b45f9a247b46bd16c7aae6c76a4

Fix typo

view details

Mara Mihali

commit sha 51efe94e782a30555cad7015be7740c5ae08d401

Add the OnError method to the COOP plugin

view details

Mara Mihali

commit sha 916a81796476aac5af50a93edfdd0c82d5612644

Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called

view details

Mara Mihali

commit sha 5f2eff00b0086b7c3a9cb017c2d828b455e054b2

Refactor the tests that use the recover function to check for != nil i.e. recover actually recovered from a panic

view details

Mara Mihali

commit sha 3207156a35658051b60cea53d2ae83357e926107

Refactored the safehttp.ResponseWriter and safehttp.Dispatcher API to have a single writing method

view details

Mara Mihali

commit sha 80111ebefb4ebf30f87547527c717fbfe185b0d4

Added helpers for creating TemplateResponse and JSONResponse objects

view details

Mara Mihali

commit sha b4f9feca009c0ba31ed2f13ee46add54532b0183

Moved the functionality provided by the safehttp.DefaultDispatcher in its write method and refactor tests

view details

Mara Mihali

commit sha 4be161316b0d808acad642fb476b135cd0e8c6e3

Reintroduce the ContentType method to the Dispatcher interface and set the CT in the safehttp.ResponseWriter. Refactor some documentation.

view details

Mara Mihali

commit sha 71397a76b6c4d5824b30077ea9ba9a492f9b53f5

Replace NewTemplateResponse and NewJSONResponse with JSONResp and TemplateResp

view details

Mara Mihali

commit sha b51d1179280b8b7cc5bb9d61c9baef0a5a3c1d7d

Not take reference to the Template and Data in the TemplateResponse since it is not actually needed

view details

Mara Mihali

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

view details

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.

maramihali

comment created time in 15 days

PullRequestReviewEvent

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.

maramihali

comment created time in 15 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

empijei

commit sha 7997723d27f7adf2c17e40ac02bad542cbd99715

coop: internal refactor

view details

Mara Mihali

commit sha 99fac467e2ff942ba24dae45499d79fcb1592044

Extended the Interceptor interface with the OnError method that will run when an error occurs

view details

Mara Mihali

commit sha 21a1a8a0e1b2c81cdf2ff24e8a751cc55813eb22

Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called

view details

Mara Mihali

commit sha 0fdaf2caa7673b45f9a247b46bd16c7aae6c76a4

Fix typo

view details

Mara Mihali

commit sha 51efe94e782a30555cad7015be7740c5ae08d401

Add the OnError method to the COOP plugin

view details

Mara Mihali

commit sha 916a81796476aac5af50a93edfdd0c82d5612644

Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called

view details

Mara Mihali

commit sha 5f2eff00b0086b7c3a9cb017c2d828b455e054b2

Refactor the tests that use the recover function to check for != nil i.e. recover actually recovered from a panic

view details

Mara Mihali

commit sha 8a1775b20c56a3f5dac48665c153efcbd8fd3e01

Implemented support for Angular's XHR XSRF protection

view details

Mara Mihali

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.

view details

Mara Mihali

commit sha 8eb6872d3609823762a6469b851439f6b46d4805

Ensure the XSRF plugins satisfy the safehttp.Interceptor interface by adding the OnError methods

view details

push time in 16 days

push eventgoogle/go-safeweb

Mara Mihali

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.

view details

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.

maramihali

comment created time in 16 days

PullRequestReviewEvent

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.

maramihali

comment created time in 16 days

PullRequestReviewEvent

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?

maramihali

comment created time in 16 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 01d472f4d6007df0a09d9a1855fe3dd36d885106

Replace NewTemplateResponse and NewJSONResponse with JSONResp and TemplateResp

view details

Mara Mihali

commit sha 81fe5c8dc7f2ffe9cfd39964662b7e2d53edcf85

Not take reference to the Template and Data in the TemplateResponse since it is not actually needed

view details

push time in 16 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 8e99b572b8be7c413784fdd1cbd958951fc4e029

Implemented support for Angular's XHR XSRF protection

view details

push time in 16 days

PR opened google/go-safeweb

Reviewers
Created a XSRF plugin for Angular enhancement plugin

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

+260 -0

0 comment

2 changed files

pr created time in 16 days

create barnchgoogle/go-safeweb

branch : maramihali-angular-xsrf

created branch time in 16 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha b9de3626054ca01270c7d3e9c1c45ba9fa6f78ff

Reintroduce the ContentType method to the Dispatcher interface and set the CT in the safehttp.ResponseWriter. Refactor some documentation.

view details

push time in 16 days

PR opened google/go-safeweb

Reviewers
Refactor the tests that use the recover() function hacktoberfest-accepted internal cleanup

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.

+48 -32

0 comment

6 changed files

pr created time in 16 days

create barnchgoogle/go-safeweb

branch : maramihali-refactor-recover

created branch 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.

maramihali

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

maramihali

comment created time in 16 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 3cb9a71d37d217fa4f6bdf293cccd917be759c0b

Added TODO about whether OnError should be notified the Commit phase of the interceptor was already called

view details

push time in 16 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha 6061277419bcb621a649dfa76614dbb8e7a78936

Add the OnError method to the COOP plugin

view details

push time in 17 days

PR closed google/go-safeweb

Reviewers
Implemented XSRF protection for Angular enhancement hacktoberfest-accepted plugin

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.

+193 -10

1 comment

3 changed files

maramihali

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.

maramihali

comment created time in 17 days

PullRequestEvent

PR closed google/go-safeweb

Reviewers
Implemented XSRF protection for Angular enhancement hacktoberfest-accepted plugin

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.

+193 -10

0 comment

3 changed files

maramihali

pr closed time in 17 days

push eventgoogle/go-safeweb

empijei

commit sha 763283a19bd42e2e953f42f5a9f79dffcfd0b043

Added COOP implementation.

view details

empijei

commit sha 99cac8da76106931d50a6df050f05f0e55bf7c9c

Added copyright to COOP plugin.

view details

empijei

commit sha 13783527bae3ac0a2250a4c69ca67888e732dc8d

coop: rename some unclear variables, add some docs.

view details

empijei

commit sha 514128f3f101da67a388966e579cf449a37304b5

htmlinject: provide helpers to load templates in a safe and ergonomic way

view details

empijei

commit sha 140071403cb62c8ad873813b54fa29c677ab5ef3

htmlinject: provide clearer configs

view details

empijei

commit sha 150831a41f1c6440f5ffa3a6945169ae5964af0a

htmlinject: address CR comments.

view details

Mara Mihali

commit sha 1264660868372c3aede30fbbb8e87c87a1fd424b

Extended the Interceptor interface with the OnError method that will run when an error occurs

view details

Mara Mihali

commit sha 6fb0512158f04d2ed0cbf77de8b7993abe87e490

Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called

view details

Mara Mihali

commit sha be9220cf8c881ce813009ccfd3c552aff353d864

Fix typo

view details

push time in 17 days

PR opened google/go-safeweb

Reviewers
Refactored the safehttp.ResponseWriter and safehttp.Dispatcher to have a single Write method, moving the other functionality inside the safehttp.DefaultDispatcher enhancement hacktoberfest-accepted internal cleanup

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.

+114 -207

0 comment

9 changed files

pr created time in 17 days

create barnchgoogle/go-safeweb

branch : maramihali-simplify-writing

created branch 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?

maramihali

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)

maramihali

comment created time in 17 days

PullRequestReviewEvent
PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha efcbea1e8e2677a4db995ec06dcead7aacee1391

Fix typo

view details

push time in 17 days

Pull request review commentgoogle/go-safeweb

coop: internal refactor

 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?

empijei

comment created time in 17 days

PullRequestReviewEvent
PullRequestReviewEvent

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.

maramihali

comment created time in 17 days

PullRequestReviewEvent

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.

maramihali

comment created time in 17 days

PullRequestReviewEvent

push eventgoogle/go-safeweb

Mara Mihali

commit sha 2e61f79792f93bd076a2b7f27badc7412920365a

Extended the Interceptor interface with the OnError method that will run when an error occurs

view details

Mara Mihali

commit sha 050396953242366eed09e49abae6d9360f9ea80a

Created the onErrorPhase that will run the OnError method of all the safehttp.Interceptor when ResponseWriter.WriteError is called

view details

push time in 17 days

push eventgoogle/go-safeweb

Mara Mihali

commit sha e4e273480803efa97b1935fed401ea8d9e3e2da7

more work

view details

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 secretAppKey and cookieID in the Inject function alongside the request and Generate generates the token either using Tink or xsrftoken

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?)
maramihali

comment created time in 17 days

PullRequestReviewEvent
more