fix: check Origin to match configured CORS Origin

This commit is contained in:
Thomas Müller 2023-06-13 15:58:42 +02:00
parent d3ef0e366d
commit af88b88ea5
3 changed files with 197 additions and 73 deletions

View File

@ -9,29 +9,10 @@ import (
) )
func TestCORS(t *testing.T) { func TestCORS(t *testing.T) {
SubTest(t, "Preflight", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { SubTest(t, "PreFlight - Conditional allow methods", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
handler, _ := NewHandler(Config{
StoreComposer: composer,
})
(&httpTest{
Method: "OPTIONS",
ReqHeader: map[string]string{
"Origin": "tus.io",
},
Code: http.StatusOK,
ResHeader: map[string]string{
"Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat",
"Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS, GET, DELETE",
"Access-Control-Max-Age": "86400",
"Access-Control-Allow-Origin": "tus.io",
},
}).Run(handler, t)
})
SubTest(t, "Conditional allow methods", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
handler, _ := NewHandler(Config{ handler, _ := NewHandler(Config{
StoreComposer: composer, StoreComposer: composer,
CorsOrigin: "https://tus.io",
DisableTermination: true, DisableTermination: true,
DisableDownload: true, DisableDownload: true,
}) })
@ -39,37 +20,185 @@ func TestCORS(t *testing.T) {
(&httpTest{ (&httpTest{
Method: "OPTIONS", Method: "OPTIONS",
ReqHeader: map[string]string{ ReqHeader: map[string]string{
"Origin": "tus.io", "Origin": "https://tus.io",
}, },
Code: http.StatusOK, Code: http.StatusOK,
ResHeader: map[string]string{ ResHeader: map[string]string{
"Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat", "Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat",
"Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS", "Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS",
"Access-Control-Max-Age": "86400", "Access-Control-Max-Age": "86400",
"Access-Control-Allow-Origin": "tus.io", "Access-Control-Allow-Origin": "https://tus.io",
}, },
}).Run(handler, t) }).Run(handler, t)
}) })
SubTest(t, "PreFlight - No Origin configured", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
SubTest(t, "Request", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
handler, _ := NewHandler(Config{ handler, _ := NewHandler(Config{
StoreComposer: composer, StoreComposer: composer,
CorsOrigin: "",
}) })
(&httpTest{ (&httpTest{
Name: "Actual request", Method: "OPTIONS",
Method: "GET", DisallowedResHeader: []string{
ReqHeader: map[string]string{ "Access-Control-Allow-Origin",
"Origin": "tus.io", "Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Max-Age",
}, },
Code: http.StatusMethodNotAllowed, Code: http.StatusOK,
ResHeader: map[string]string{ ReqHeader: map[string]string{
"Access-Control-Expose-Headers": "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat", "Origin": "https://tus.io",
"Access-Control-Allow-Origin": "tus.io",
}, },
}).Run(handler, t) }).Run(handler, t)
}) })
SubTest(t, "PreFlight - Disabled CORS", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
handler, _ := NewHandler(Config{
StoreComposer: composer,
CorsOrigin: "",
DisableCors: true,
})
(&httpTest{
Method: "OPTIONS",
DisallowedResHeader: []string{
"Access-Control-Allow-Origin",
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Max-Age",
},
Code: http.StatusOK,
ReqHeader: map[string]string{
"Origin": "https://tus.io",
},
}).Run(handler, t)
})
SubTest(t, "PreFlight - Wildcard Origin", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
handler, _ := NewHandler(Config{
StoreComposer: composer,
CorsOrigin: "*",
})
(&httpTest{
Method: "OPTIONS",
ResHeader: map[string]string{
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS, GET, DELETE",
"Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat",
"Access-Control-Max-Age": "86400",
},
Code: http.StatusOK,
ReqHeader: map[string]string{
"Origin": "https://tus.io",
},
}).Run(handler, t)
})
SubTest(t, "PreFlight - Matching Origin", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
handler, _ := NewHandler(Config{
StoreComposer: composer,
CorsOrigin: "https://tus.io",
})
(&httpTest{
Method: "OPTIONS",
ResHeader: map[string]string{
"Access-Control-Allow-Origin": "https://tus.io",
"Access-Control-Allow-Methods": "POST, HEAD, PATCH, OPTIONS, GET, DELETE",
"Access-Control-Allow-Headers": "Authorization, Origin, X-Requested-With, X-Request-ID, X-HTTP-Method-Override, Content-Type, Upload-Length, Upload-Offset, Tus-Resumable, Upload-Metadata, Upload-Defer-Length, Upload-Concat",
"Access-Control-Max-Age": "86400",
},
Code: http.StatusOK,
ReqHeader: map[string]string{
"Origin": "https://tus.io",
},
}).Run(handler, t)
})
SubTest(t, "PreFlight - Not Matching Origin", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
handler, _ := NewHandler(Config{
StoreComposer: composer,
CorsOrigin: "https://tus.net",
})
(&httpTest{
Method: "OPTIONS",
DisallowedResHeader: []string{
"Access-Control-Allow-Origin",
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Max-Age",
},
Code: http.StatusOK,
ReqHeader: map[string]string{
"Origin": "https://tus.io",
},
}).Run(handler, t)
})
SubTest(t, "Actual Request - Wildcard Origin", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
handler, _ := NewHandler(Config{
StoreComposer: composer,
CorsOrigin: "*",
})
(&httpTest{
Method: "POST",
ResHeader: map[string]string{
"Access-Control-Allow-Origin": "*",
"Access-Control-Expose-Headers": "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat",
},
DisallowedResHeader: []string{
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Max-Age",
},
Code: http.StatusPreconditionFailed,
ReqHeader: map[string]string{
"Origin": "https://tus.io",
},
}).Run(handler, t)
})
SubTest(t, "Actual Request - Matching Origin", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
composer = NewStoreComposer()
composer.UseCore(store)
handler, _ := NewHandler(Config{
StoreComposer: composer,
CorsOrigin: "https://tus.io",
})
(&httpTest{
Method: "POST",
ResHeader: map[string]string{
"Access-Control-Allow-Origin": "https://tus.io",
"Access-Control-Expose-Headers": "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat",
},
DisallowedResHeader: []string{
"Access-Control-Allow-Methods",
"Access-Control-Allow-Headers",
"Access-Control-Max-Age",
},
Code: http.StatusPreconditionFailed,
ReqHeader: map[string]string{
"Origin": "https://tus.io",
},
}).Run(handler, t)
})
SubTest(t, "AppendHeaders", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) { SubTest(t, "AppendHeaders", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
handler, _ := NewHandler(Config{ handler, _ := NewHandler(Config{
StoreComposer: composer, StoreComposer: composer,
@ -77,7 +206,7 @@ func TestCORS(t *testing.T) {
req, _ := http.NewRequest("OPTIONS", "", nil) req, _ := http.NewRequest("OPTIONS", "", nil)
req.Header.Set("Tus-Resumable", "1.0.0") req.Header.Set("Tus-Resumable", "1.0.0")
req.Header.Set("Origin", "tus.io") req.Header.Set("Origin", "https://tus.io")
req.Host = "tus.io" req.Host = "tus.io"
res := httptest.NewRecorder() res := httptest.NewRecorder()
@ -96,20 +225,4 @@ func TestCORS(t *testing.T) {
t.Errorf("expected header to contain METHOD but got: %#v", methods) t.Errorf("expected header to contain METHOD but got: %#v", methods)
} }
}) })
SubTest(t, "Disable CORS", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
handler, _ := NewHandler(Config{
StoreComposer: composer,
DisableCors: true,
})
(&httpTest{
Method: "OPTIONS",
ReqHeader: map[string]string{
"Origin": "tus.io",
},
Code: http.StatusOK,
ResHeader: map[string]string{},
}).Run(handler, t)
})
} }

View File

@ -225,9 +225,10 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
if origin := r.Header.Get("Origin"); !handler.config.DisableCors && origin != "" { if origin := r.Header.Get("Origin"); !handler.config.DisableCors && origin != "" {
var configuredOrigin = handler.config.CorsOrigin var configuredOrigin = handler.config.CorsOrigin
if configuredOrigin != "" { if configuredOrigin == "*" {
origin = configuredOrigin origin = "*"
} }
if configuredOrigin == origin {
header.Set("Access-Control-Allow-Origin", origin) header.Set("Access-Control-Allow-Origin", origin)
header.Set("Vary", "Origin") header.Set("Vary", "Origin")
@ -251,6 +252,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
header.Add("Access-Control-Expose-Headers", "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat") header.Add("Access-Control-Expose-Headers", "Upload-Offset, Location, Upload-Length, Tus-Version, Tus-Resumable, Tus-Max-Size, Tus-Extension, Upload-Metadata, Upload-Defer-Length, Upload-Concat")
} }
} }
}
// Set current version used by the server // Set current version used by the server
header.Set("Tus-Resumable", "1.0.0") header.Set("Tus-Resumable", "1.0.0")

View File

@ -55,6 +55,7 @@ type httpTest struct {
Code int Code int
ResBody string ResBody string
ResHeader map[string]string ResHeader map[string]string
DisallowedResHeader []string
} }
func (test *httpTest) Run(handler http.Handler, t *testing.T) *httptest.ResponseRecorder { func (test *httpTest) Run(handler http.Handler, t *testing.T) *httptest.ResponseRecorder {
@ -82,6 +83,14 @@ func (test *httpTest) Run(handler http.Handler, t *testing.T) *httptest.Response
} }
} }
for _, key := range test.DisallowedResHeader {
header := w.Header().Get(key)
if header != "" {
t.Errorf("Not Expected '%s' (got '%s')", key, header)
}
}
if test.ResBody != "" && w.Body.String() != test.ResBody { if test.ResBody != "" && w.Body.String() != test.ResBody {
t.Errorf("Expected '%s' as body (got '%s'", test.ResBody, w.Body.String()) t.Errorf("Expected '%s' as body (got '%s'", test.ResBody, w.Body.String())
} }