github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/safehttp/plugins/fetchmetadata/resource_test.go (about)

     1  // Copyright 2022 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fetchmetadata_test
    16  
    17  import (
    18  	"net/http"
    19  	"testing"
    20  
    21  	"github.com/google/go-cmp/cmp"
    22  	"github.com/google/go-safeweb/safehttp/plugins/fetchmetadata"
    23  	"github.com/google/go-safeweb/safehttp/plugins/fetchmetadata/internalunsafefetchmetadata/unsafefetchmetadatafortests"
    24  
    25  	"github.com/google/go-safeweb/safehttp"
    26  	"github.com/google/go-safeweb/safehttp/safehttptest"
    27  )
    28  
    29  var (
    30  	allowedRIPHeaders = []testHeaders{
    31  		{
    32  			name:   "Fetch Metadata not supported",
    33  			method: safehttp.MethodGet,
    34  			site:   "",
    35  		},
    36  		{
    37  			name:   "same origin",
    38  			method: safehttp.MethodGet,
    39  			site:   "same-origin",
    40  		},
    41  		{
    42  			name:   "same site",
    43  			method: safehttp.MethodGet,
    44  			site:   "same-site",
    45  		},
    46  		{
    47  			name:   "user agent initiated",
    48  			method: safehttp.MethodGet,
    49  			site:   "none",
    50  		},
    51  		{
    52  			name:   "cors bug missing mode",
    53  			method: safehttp.MethodOptions,
    54  			site:   "cross-site",
    55  			mode:   "",
    56  		},
    57  	}
    58  
    59  	allowedRIPNavHeaders = []testHeaders{
    60  		{
    61  			name:   "cross origin GET navigate from document",
    62  			method: safehttp.MethodGet,
    63  			site:   "cross-site",
    64  			mode:   "navigate",
    65  			dest:   "document",
    66  		},
    67  		{
    68  			name:   "cross origin HEAD navigate from document",
    69  			method: safehttp.MethodHead,
    70  			site:   "cross-site",
    71  			mode:   "navigate",
    72  			dest:   "document",
    73  		},
    74  		{
    75  			name:   "cross origin GET navigate from nested-document",
    76  			method: safehttp.MethodGet,
    77  			site:   "cross-site",
    78  			mode:   "navigate",
    79  			dest:   "nested-document",
    80  		},
    81  		{
    82  			name:   "cross origin HEAD navigate from nested-document",
    83  			method: safehttp.MethodHead,
    84  			site:   "cross-site",
    85  			mode:   "navigate",
    86  			dest:   "nested-document",
    87  		},
    88  		{
    89  			name:   "cross origin GET nested-navigate from document",
    90  			method: safehttp.MethodGet,
    91  			site:   "cross-site",
    92  			mode:   "nested-navigate",
    93  			dest:   "document",
    94  		},
    95  		{
    96  			name:   "cross origin HEAD nested-navigate from document",
    97  			method: safehttp.MethodHead,
    98  			site:   "cross-site",
    99  			mode:   "nested-navigate",
   100  			dest:   "document",
   101  		},
   102  		{
   103  			name:   "cross origin GET nested-navigate from nested-document",
   104  			method: safehttp.MethodGet,
   105  			site:   "cross-site",
   106  			mode:   "nested-navigate",
   107  			dest:   "nested-document",
   108  		},
   109  		{
   110  			name:   "cross origin HEAD nested-navigate from nested-document",
   111  			method: safehttp.MethodHead,
   112  			site:   "cross-site",
   113  			mode:   "nested-navigate",
   114  			dest:   "nested-document",
   115  		},
   116  	}
   117  
   118  	disallowedRIPNavHeaders = []testHeaders{
   119  		{
   120  			name:   "cross origin POST",
   121  			method: safehttp.MethodPost,
   122  			site:   "cross-site",
   123  			mode:   "navigate",
   124  			dest:   "document",
   125  		},
   126  		{
   127  			name:   "cross origin GET from object",
   128  			method: safehttp.MethodGet,
   129  			site:   "cross-site",
   130  			mode:   "navigate",
   131  			dest:   "object",
   132  		},
   133  		{
   134  			name:   "cross origin HEAD from embed",
   135  			method: safehttp.MethodHead,
   136  			site:   "cross-site",
   137  			mode:   "navigate",
   138  			dest:   "embed",
   139  		},
   140  	}
   141  
   142  	disallowedRIPHeaders = []testHeaders{
   143  		{
   144  			name:   "cross origin no cors",
   145  			method: safehttp.MethodPost,
   146  			site:   "cross-site",
   147  			mode:   "cors",
   148  			dest:   "document",
   149  		},
   150  		{
   151  			name:   "cross origin no cors",
   152  			method: safehttp.MethodPost,
   153  			site:   "cross-site",
   154  			mode:   "no-cors",
   155  			dest:   "nested-document",
   156  		},
   157  	}
   158  )
   159  
   160  func TestAllowedResourceIsolationEnforceMode(t *testing.T) {
   161  	tests := append(allowedRIPHeaders, allowedRIPNavHeaders...)
   162  	for _, test := range tests {
   163  		t.Run(test.name, func(t *testing.T) {
   164  			req := safehttptest.NewRequest(test.method, "https://spaghetti.com/carbonara", nil)
   165  			req.Header.Add("Sec-Fetch-Site", test.site)
   166  			req.Header.Add("Sec-Fetch-Mode", test.mode)
   167  			req.Header.Add("Sec-Fetch-Dest", test.dest)
   168  			fakeRW, rr := safehttptest.NewFakeResponseWriter()
   169  
   170  			p := fetchmetadata.ResourceIsolationPolicy()
   171  			p.Before(fakeRW, req, nil)
   172  
   173  			if want, got := int(safehttp.StatusOK), rr.Code; got != want {
   174  				t.Errorf("rr.Code got: %v want: %v", got, want)
   175  			}
   176  			if diff := cmp.Diff(http.Header{}, rr.Header()); diff != "" {
   177  				t.Errorf("rr.Header() mismatch (-want +got):\n%s", diff)
   178  			}
   179  			if want, got := "", rr.Body.String(); got != want {
   180  				t.Errorf("rr.Body.String() got: %q want: %q", got, want)
   181  			}
   182  		})
   183  	}
   184  }
   185  
   186  func TestRejectedResourceIsolationEnforceMode(t *testing.T) {
   187  	tests := append(disallowedRIPHeaders, disallowedRIPNavHeaders...)
   188  	for _, test := range tests {
   189  		t.Run(test.name, func(t *testing.T) {
   190  			req := safehttptest.NewRequest(test.method, "https://spaghetti.com/carbonara", nil)
   191  			req.Header.Add("Sec-Fetch-Site", test.site)
   192  			req.Header.Add("Sec-Fetch-Mode", test.mode)
   193  			req.Header.Add("Sec-Fetch-Dest", test.dest)
   194  			fakeRW, rr := safehttptest.NewFakeResponseWriter()
   195  
   196  			p := fetchmetadata.ResourceIsolationPolicy()
   197  			p.Before(fakeRW, req, nil)
   198  
   199  			if want, got := safehttp.StatusForbidden, safehttp.StatusCode(rr.Code); want != got {
   200  				t.Errorf("rr.Code got: %v want: %v", got, want)
   201  			}
   202  			if diff := cmp.Diff(http.Header{}, rr.Header()); diff != "" {
   203  				t.Errorf("rr.Header() mismatch (-want +got):\n%s", diff)
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestDisableResourceIsolationPolicy(t *testing.T) {
   210  	type reportTests struct {
   211  		name, method, site, mode, dest, blocked string
   212  	}
   213  	var tests []reportTests
   214  	for _, t := range append(allowedRIPNavHeaders, allowedRIPHeaders...) {
   215  		tests = append(tests, reportTests{
   216  			name:    t.name,
   217  			method:  t.method,
   218  			site:    t.site,
   219  			mode:    t.mode,
   220  			dest:    t.dest,
   221  			blocked: "false",
   222  		})
   223  	}
   224  	for _, t := range append(disallowedRIPNavHeaders, disallowedRIPHeaders...) {
   225  		tests = append(tests, reportTests{
   226  			name:    t.name,
   227  			method:  t.method,
   228  			site:    t.site,
   229  			mode:    t.mode,
   230  			dest:    t.dest,
   231  			blocked: "true",
   232  		})
   233  	}
   234  	for _, test := range tests {
   235  		t.Run(test.name, func(t *testing.T) {
   236  			req := safehttptest.NewRequest(test.method, "https://spaghetti.com/carbonara", nil)
   237  			req.Header.Add("Sec-Fetch-Site", test.site)
   238  			req.Header.Add("Sec-Fetch-Mode", test.mode)
   239  			req.Header.Add("Sec-Fetch-Dest", test.dest)
   240  			fakeRW, rr := safehttptest.NewFakeResponseWriter()
   241  
   242  			p := fetchmetadata.ResourceIsolationPolicy()
   243  			p.Before(fakeRW, req, unsafefetchmetadatafortests.DisableResourceIsolationPolicy())
   244  
   245  			if want, got := safehttp.StatusOK, safehttp.StatusCode(rr.Code); want != got {
   246  				t.Errorf("rr.Code got: %v want: %v", got, want)
   247  			}
   248  			if diff := cmp.Diff(http.Header{}, rr.Header()); diff != "" {
   249  				t.Errorf("rr.Header() mismatch (-want +got):\n%s", diff)
   250  			}
   251  			if want, got := "", rr.Body.String(); got != want {
   252  				t.Errorf("rr.Body.String() got: %q want: %q", got, want)
   253  			}
   254  		})
   255  	}
   256  }