http servers: allow CORS to be set with --allow-origin flag - fixes #5078
Some changes about test cases: Because MiddlewareCORS will return early on OPTIONS request, this middleware should only be used once at NewServer function. Test cases should pass AllowOrigin config instead of adding this middleware again. A new test case was added to test CORS preflight request with an authenticator. Preflight request should always return 200 OK regardless of autentications. Co-authored-by: yuudi <yuudi@users.noreply.github.com>
This commit is contained in:
@@ -181,6 +181,13 @@ func MiddlewareCORS(allowOrigin string) Middleware {
|
||||
w.Header().Add("Access-Control-Request-Method", "POST, OPTIONS, GET, HEAD")
|
||||
w.Header().Add("Access-Control-Allow-Headers", "authorization, Content-Type")
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
// Because CORS preflight OPTIONS requests are not authenticated,
|
||||
// and require a 200 OK response, we will return early here.
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
+69
-11
@@ -329,23 +329,22 @@ var _testCORSHeaderKeys = []string{
|
||||
|
||||
func TestMiddlewareCORS(t *testing.T) {
|
||||
servers := []struct {
|
||||
name string
|
||||
http Config
|
||||
origin string
|
||||
name string
|
||||
http Config
|
||||
}{
|
||||
{
|
||||
name: "EmptyOrigin",
|
||||
http: Config{
|
||||
ListenAddr: []string{"127.0.0.1:0"},
|
||||
ListenAddr: []string{"127.0.0.1:0"},
|
||||
AllowOrigin: "",
|
||||
},
|
||||
origin: "",
|
||||
},
|
||||
{
|
||||
name: "CustomOrigin",
|
||||
http: Config{
|
||||
ListenAddr: []string{"127.0.0.1:0"},
|
||||
ListenAddr: []string{"127.0.0.1:0"},
|
||||
AllowOrigin: "http://test.rclone.org",
|
||||
},
|
||||
origin: "http://test.rclone.org",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -357,8 +356,6 @@ func TestMiddlewareCORS(t *testing.T) {
|
||||
require.NoError(t, s.Shutdown())
|
||||
}()
|
||||
|
||||
s.Router().Use(MiddlewareCORS(ss.origin))
|
||||
|
||||
expected := []byte("data")
|
||||
s.Router().Mount("/", testEchoHandler(expected))
|
||||
s.Serve()
|
||||
@@ -384,8 +381,69 @@ func TestMiddlewareCORS(t *testing.T) {
|
||||
}
|
||||
|
||||
expectedOrigin := url
|
||||
if ss.origin != "" {
|
||||
expectedOrigin = ss.origin
|
||||
if ss.http.AllowOrigin != "" {
|
||||
expectedOrigin = ss.http.AllowOrigin
|
||||
}
|
||||
require.Equal(t, expectedOrigin, resp.Header.Get("Access-Control-Allow-Origin"), "allow origin should match")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiddlewareCORSWithAuth(t *testing.T) {
|
||||
authServers := []struct {
|
||||
name string
|
||||
http Config
|
||||
auth AuthConfig
|
||||
}{
|
||||
{
|
||||
name: "ServerWithAuth",
|
||||
http: Config{
|
||||
ListenAddr: []string{"127.0.0.1:0"},
|
||||
AllowOrigin: "http://test.rclone.org",
|
||||
},
|
||||
auth: AuthConfig{
|
||||
Realm: "test",
|
||||
BasicUser: "test_user",
|
||||
BasicPass: "test_pass",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, ss := range authServers {
|
||||
t.Run(ss.name, func(t *testing.T) {
|
||||
s, err := NewServer(context.Background(), WithConfig(ss.http))
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
require.NoError(t, s.Shutdown())
|
||||
}()
|
||||
|
||||
expected := []byte("data")
|
||||
s.Router().Mount("/", testEchoHandler(expected))
|
||||
s.Serve()
|
||||
|
||||
url := testGetServerURL(t, s)
|
||||
|
||||
client := &http.Client{}
|
||||
req, err := http.NewRequest("OPTIONS", url, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
require.NoError(t, err)
|
||||
defer func() {
|
||||
_ = resp.Body.Close()
|
||||
}()
|
||||
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, "OPTIONS should return ok even if not authenticated")
|
||||
|
||||
testExpectRespBody(t, resp, []byte{})
|
||||
|
||||
for _, key := range _testCORSHeaderKeys {
|
||||
require.Contains(t, resp.Header, key, "CORS headers should be sent even if not authenticated")
|
||||
}
|
||||
|
||||
expectedOrigin := url
|
||||
if ss.http.AllowOrigin != "" {
|
||||
expectedOrigin = ss.http.AllowOrigin
|
||||
}
|
||||
require.Equal(t, expectedOrigin, resp.Header.Get("Access-Control-Allow-Origin"), "allow origin should match")
|
||||
})
|
||||
|
||||
@@ -109,6 +109,7 @@ type Config struct {
|
||||
TLSKeyBody []byte // TLS PEM Private key body, ignores TLSKey
|
||||
ClientCA string // Client certificate authority to verify clients with
|
||||
MinTLSVersion string // MinTLSVersion contains the minimum TLS version that is acceptable.
|
||||
AllowOrigin string // AllowOrigin sets the Access-Control-Allow-Origin header
|
||||
}
|
||||
|
||||
// AddFlagsPrefix adds flags for the httplib
|
||||
@@ -122,6 +123,7 @@ func (cfg *Config) AddFlagsPrefix(flagSet *pflag.FlagSet, prefix string) {
|
||||
flags.StringVarP(flagSet, &cfg.ClientCA, prefix+"client-ca", "", cfg.ClientCA, "Client certificate authority to verify clients with")
|
||||
flags.StringVarP(flagSet, &cfg.BaseURL, prefix+"baseurl", "", cfg.BaseURL, "Prefix for URLs - leave blank for root")
|
||||
flags.StringVarP(flagSet, &cfg.MinTLSVersion, prefix+"min-tls-version", "", cfg.MinTLSVersion, "Minimum TLS version that is acceptable")
|
||||
flags.StringVarP(flagSet, &cfg.AllowOrigin, prefix+"allow-origin", "", cfg.AllowOrigin, "Origin which cross-domain request (CORS) can be executed from")
|
||||
}
|
||||
|
||||
// AddHTTPFlagsPrefix adds flags for the httplib
|
||||
@@ -236,6 +238,8 @@ func NewServer(ctx context.Context, options ...Option) (*Server, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.mux.Use(MiddlewareCORS(s.cfg.AllowOrigin))
|
||||
|
||||
s.initAuth()
|
||||
|
||||
for _, addr := range s.cfg.ListenAddr {
|
||||
|
||||
@@ -82,8 +82,6 @@ func TestNewServerUnix(t *testing.T) {
|
||||
|
||||
require.Empty(t, s.URLs(), "unix socket should not appear in URLs")
|
||||
|
||||
s.Router().Use(MiddlewareCORS(""))
|
||||
|
||||
expected := []byte("hello world")
|
||||
s.Router().Mount("/", testEchoHandler(expected))
|
||||
s.Serve()
|
||||
|
||||
Reference in New Issue
Block a user