Compare commits

..

No commits in common. "7a2786ca6cd84633784a2c9986da65a9c4d79c78" and "5b104a55331a5774140a7d35ab363a59a8aec9c8" have entirely different histories.

13 changed files with 84 additions and 142 deletions

View File

@ -1156,9 +1156,15 @@ LEVEL = Info
;; enable cors headers (disabled by default) ;; enable cors headers (disabled by default)
;ENABLED = false ;ENABLED = false
;; ;;
;; list of requesting origins that are allowed, eg: "https://*.example.com" ;; scheme of allowed requests
;SCHEME = http
;;
;; list of requesting domains that are allowed
;ALLOW_DOMAIN = * ;ALLOW_DOMAIN = *
;; ;;
;; allow subdomains of headers listed above to request
;ALLOW_SUBDOMAIN = false
;;
;; list of methods allowed to request ;; list of methods allowed to request
;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS ;METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
;; ;;

View File

@ -196,7 +196,9 @@ The following configuration set `Content-Type: application/vnd.android.package-a
## CORS (`cors`) ## CORS (`cors`)
- `ENABLED`: **false**: enable cors headers (disabled by default) - `ENABLED`: **false**: enable cors headers (disabled by default)
- `ALLOW_DOMAIN`: **\***: list of requesting origins that are allowed, eg: "https://*.example.com" - `SCHEME`: **http**: scheme of allowed requests
- `ALLOW_DOMAIN`: **\***: list of requesting domains that are allowed
- `ALLOW_SUBDOMAIN`: **false**: allow subdomains of headers listed above to request
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request - `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: list of methods allowed to request
- `MAX_AGE`: **10m**: max time to cache response - `MAX_AGE`: **10m**: max time to cache response
- `ALLOW_CREDENTIALS`: **false**: allow request with credentials - `ALLOW_CREDENTIALS`: **false**: allow request with credentials

View File

@ -195,7 +195,9 @@ menu:
## 跨域 (`cors`) ## 跨域 (`cors`)
- `ENABLED`: **false**: 启用 CORS 头部(默认禁用) - `ENABLED`: **false**: 启用 CORS 头部(默认禁用)
- `SCHEME`: **http**: 允许请求的协议
- `ALLOW_DOMAIN`: **\***: 允许请求的域名列表 - `ALLOW_DOMAIN`: **\***: 允许请求的域名列表
- `ALLOW_SUBDOMAIN`: **false**: 允许上述列出的头部的子域名发出请求。
- `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: 允许发起的请求方式列表 - `METHODS`: **GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS**: 允许发起的请求方式列表
- `MAX_AGE`: **10m**: 缓存响应的最大时间 - `MAX_AGE`: **10m**: 缓存响应的最大时间
- `ALLOW_CREDENTIALS`: **false**: 允许带有凭据的请求 - `ALLOW_CREDENTIALS`: **false**: 允许带有凭据的请求

View File

@ -1153,14 +1153,9 @@ func DeleteComment(ctx context.Context, comment *Comment) error {
// UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id // UpdateCommentsMigrationsByType updates comments' migrations information via given git service type and original id and poster id
func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error { func UpdateCommentsMigrationsByType(ctx context.Context, tp structs.GitServiceType, originalAuthorID string, posterID int64) error {
_, err := db.GetEngine(ctx).Table("comment"). _, err := db.GetEngine(ctx).Table("comment").
Where(builder.In("issue_id", Join("INNER", "issue", "issue.id = comment.issue_id").
builder.Select("issue.id"). Join("INNER", "repository", "issue.repo_id = repository.id").
From("issue"). Where("repository.original_service_type = ?", tp).
InnerJoin("repository", "issue.repo_id = repository.id").
Where(builder.Eq{
"repository.original_service_type": tp,
}),
)).
And("comment.original_author_id = ?", originalAuthorID). And("comment.original_author_id = ?", originalAuthorID).
Update(map[string]any{ Update(map[string]any{
"poster_id": posterID, "poster_id": posterID,

View File

@ -33,7 +33,7 @@ func FileHandlerFunc() http.HandlerFunc {
assetFS := AssetFS() assetFS := AssetFS()
return func(resp http.ResponseWriter, req *http.Request) { return func(resp http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "HEAD" { if req.Method != "GET" && req.Method != "HEAD" {
resp.WriteHeader(http.StatusMethodNotAllowed) resp.WriteHeader(http.StatusNotFound)
return return
} }
handleRequest(resp, req, assetFS, req.URL.Path) handleRequest(resp, req, assetFS, req.URL.Path)

View File

@ -12,7 +12,9 @@ import (
// CORSConfig defines CORS settings // CORSConfig defines CORS settings
var CORSConfig = struct { var CORSConfig = struct {
Enabled bool Enabled bool
AllowDomain []string // FIXME: this option is from legacy code, it actually works as "AllowedOrigins". When refactoring in the future, the config option should also be renamed together. Scheme string
AllowDomain []string
AllowSubdomain bool
Methods []string Methods []string
MaxAge time.Duration MaxAge time.Duration
AllowCredentials bool AllowCredentials bool

View File

@ -101,18 +101,16 @@ func (r *Route) wrapMiddlewareAndHandler(h []any) ([]func(http.Handler) http.Han
return middlewares, handlerFunc return middlewares, handlerFunc
} }
// Methods adds the same handlers for multiple http "methods" (separated by ","). func (r *Route) Methods(method, pattern string, h ...any) {
// If any method is invalid, the lower level router will panic.
func (r *Route) Methods(methods, pattern string, h ...any) {
middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h) middlewares, handlerFunc := r.wrapMiddlewareAndHandler(h)
fullPattern := r.getPattern(pattern) fullPattern := r.getPattern(pattern)
if strings.Contains(methods, ",") { if strings.Contains(method, ",") {
methods := strings.Split(methods, ",") methods := strings.Split(method, ",")
for _, method := range methods { for _, method := range methods {
r.R.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc) r.R.With(middlewares...).Method(strings.TrimSpace(method), fullPattern, handlerFunc)
} }
} else { } else {
r.R.With(middlewares...).Method(methods, fullPattern, handlerFunc) r.R.With(middlewares...).Method(method, fullPattern, handlerFunc)
} }
} }
@ -138,6 +136,20 @@ func (r *Route) Get(pattern string, h ...any) {
r.Methods("GET", pattern, h...) r.Methods("GET", pattern, h...)
} }
func (r *Route) Options(pattern string, h ...any) {
r.Methods("OPTIONS", pattern, h...)
}
// GetOptions delegate get and options method
func (r *Route) GetOptions(pattern string, h ...any) {
r.Methods("GET,OPTIONS", pattern, h...)
}
// PostOptions delegate post and options method
func (r *Route) PostOptions(pattern string, h ...any) {
r.Methods("POST,OPTIONS", pattern, h...)
}
// Head delegate head method // Head delegate head method
func (r *Route) Head(pattern string, h ...any) { func (r *Route) Head(pattern string, h ...any) {
r.Methods("HEAD", pattern, h...) r.Methods("HEAD", pattern, h...)

6
package-lock.json generated
View File

@ -1000,9 +1000,9 @@
} }
}, },
"node_modules/@github/combobox-nav": { "node_modules/@github/combobox-nav": {
"version": "2.3.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.1.tgz", "resolved": "https://registry.npmjs.org/@github/combobox-nav/-/combobox-nav-2.3.0.tgz",
"integrity": "sha512-gwxPzLw8XKecy1nP63i9lOBritS3bWmxl02UX6G0TwMQZbMem1BCS1tEZgYd3mkrkiDrUMWaX+DbFCuDFo3K+A==" "integrity": "sha512-5CX03DbsLZ41dX5hKHyQKtg133U6lruX4TD9G0Zs4W8BpWy7lN8DJ6TYaeZN/V7x8K34coaqNYk/Y5ic7stfkg=="
}, },
"node_modules/@github/markdown-toolbar-element": { "node_modules/@github/markdown-toolbar-element": {
"version": "2.2.1", "version": "2.2.1",

View File

@ -821,7 +821,9 @@ func Routes() *web.Route {
m.Use(securityHeaders()) m.Use(securityHeaders())
if setting.CORSConfig.Enabled { if setting.CORSConfig.Enabled {
m.Use(cors.Handler(cors.Options{ m.Use(cors.Handler(cors.Options{
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
AllowedOrigins: setting.CORSConfig.AllowDomain, AllowedOrigins: setting.CORSConfig.AllowDomain,
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
AllowedMethods: setting.CORSConfig.Methods, AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials, AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...), AllowedHeaders: append([]string{"Authorization", "X-Gitea-OTP"}, setting.CORSConfig.Headers...),

View File

@ -28,16 +28,16 @@ func requireSignIn(ctx *context.Context) {
func gitHTTPRouters(m *web.Route) { func gitHTTPRouters(m *web.Route) {
m.Group("", func() { m.Group("", func() {
m.Methods("POST,OPTIONS", "/git-upload-pack", repo.ServiceUploadPack) m.PostOptions("/git-upload-pack", repo.ServiceUploadPack)
m.Methods("POST,OPTIONS", "/git-receive-pack", repo.ServiceReceivePack) m.PostOptions("/git-receive-pack", repo.ServiceReceivePack)
m.Methods("GET,OPTIONS", "/info/refs", repo.GetInfoRefs) m.GetOptions("/info/refs", repo.GetInfoRefs)
m.Methods("GET,OPTIONS", "/HEAD", repo.GetTextFile("HEAD")) m.GetOptions("/HEAD", repo.GetTextFile("HEAD"))
m.Methods("GET,OPTIONS", "/objects/info/alternates", repo.GetTextFile("objects/info/alternates")) m.GetOptions("/objects/info/alternates", repo.GetTextFile("objects/info/alternates"))
m.Methods("GET,OPTIONS", "/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates")) m.GetOptions("/objects/info/http-alternates", repo.GetTextFile("objects/info/http-alternates"))
m.Methods("GET,OPTIONS", "/objects/info/packs", repo.GetInfoPacks) m.GetOptions("/objects/info/packs", repo.GetInfoPacks)
m.Methods("GET,OPTIONS", "/objects/info/{file:[^/]*}", repo.GetTextFile("")) m.GetOptions("/objects/info/{file:[^/]*}", repo.GetTextFile(""))
m.Methods("GET,OPTIONS", "/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject) m.GetOptions("/objects/{head:[0-9a-f]{2}}/{hash:[0-9a-f]{38}}", repo.GetLooseObject)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile) m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.pack", repo.GetPackFile)
m.Methods("GET,OPTIONS", "/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile) m.GetOptions("/objects/pack/pack-{file:[0-9a-f]{40}}.idx", repo.GetIdxFile)
}, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb()) }, ignSignInAndCsrf, requireSignIn, repo.HTTPGitEnabledHandler, repo.CorsHandler(), context_service.UserAssignmentWeb())
} }

View File

@ -33,6 +33,10 @@ func DummyOK(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
} }
func DummyBadRequest(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}
func RobotsTxt(w http.ResponseWriter, req *http.Request) { func RobotsTxt(w http.ResponseWriter, req *http.Request) {
robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt") robotsTxt := util.FilePathJoinAbs(setting.CustomPath, "public/robots.txt")
if ok, _ := util.IsExist(robotsTxt); !ok { if ok, _ := util.IsExist(robotsTxt); !ok {

View File

@ -59,12 +59,13 @@ const (
GzipMinSize = 1400 GzipMinSize = 1400
) )
// optionsCorsHandler return a http handler which sets CORS options if enabled by config, it blocks non-CORS OPTIONS requests. // CorsHandler return a http handler who set CORS options if enabled by config
func optionsCorsHandler() func(next http.Handler) http.Handler { func CorsHandler() func(next http.Handler) http.Handler {
var corsHandler func(next http.Handler) http.Handler
if setting.CORSConfig.Enabled { if setting.CORSConfig.Enabled {
corsHandler = cors.Handler(cors.Options{ return cors.Handler(cors.Options{
// Scheme: setting.CORSConfig.Scheme, // FIXME: the cors middleware needs scheme option
AllowedOrigins: setting.CORSConfig.AllowDomain, AllowedOrigins: setting.CORSConfig.AllowDomain,
// setting.CORSConfig.AllowSubdomain // FIXME: the cors middleware needs allowSubdomain option
AllowedMethods: setting.CORSConfig.Methods, AllowedMethods: setting.CORSConfig.Methods,
AllowCredentials: setting.CORSConfig.AllowCredentials, AllowCredentials: setting.CORSConfig.AllowCredentials,
AllowedHeaders: setting.CORSConfig.Headers, AllowedHeaders: setting.CORSConfig.Headers,
@ -73,23 +74,7 @@ func optionsCorsHandler() func(next http.Handler) http.Handler {
} }
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return next
if r.Method == http.MethodOptions {
if corsHandler != nil && r.Header.Get("Access-Control-Request-Method") != "" {
corsHandler(next).ServeHTTP(w, r)
} else {
// it should explicitly deny OPTIONS requests if CORS handler is not executed, to avoid the next GET/POST handler being incorrectly called by the OPTIONS request
w.WriteHeader(http.StatusMethodNotAllowed)
}
return
}
// for non-OPTIONS requests, call the CORS handler to add some related headers like "Vary"
if corsHandler != nil {
corsHandler(next).ServeHTTP(w, r)
} else {
next.ServeHTTP(w, r)
}
})
} }
} }
@ -232,7 +217,7 @@ func Routes() *web.Route {
routes := web.NewRoute() routes := web.NewRoute()
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", optionsCorsHandler(), public.FileHandlerFunc()) routes.Methods("GET, HEAD", "/assets/*", CorsHandler(), public.FileHandlerFunc())
routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
@ -472,8 +457,8 @@ func registerRoutes(m *web.Route) {
m.Get("/change-password", func(ctx *context.Context) { m.Get("/change-password", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/user/settings/account") ctx.Redirect(setting.AppSubURL + "/user/settings/account")
}) })
m.Methods("GET, HEAD", "/*", public.FileHandlerFunc()) m.Any("/*", CorsHandler(), public.FileHandlerFunc())
}, optionsCorsHandler()) }, CorsHandler())
m.Group("/explore", func() { m.Group("/explore", func() {
m.Get("", func(ctx *context.Context) { m.Get("", func(ctx *context.Context) {
@ -546,11 +531,14 @@ func registerRoutes(m *web.Route) {
// TODO manage redirection // TODO manage redirection
m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth) m.Post("/authorize", web.Bind(forms.AuthorizationForm{}), auth.AuthorizeOAuth)
}, ignSignInAndCsrf, reqSignIn) }, ignSignInAndCsrf, reqSignIn)
m.Options("/login/oauth/userinfo", CorsHandler(), misc.DummyBadRequest)
m.Methods("GET, OPTIONS", "/login/oauth/userinfo", optionsCorsHandler(), ignSignInAndCsrf, auth.InfoOAuth) m.Get("/login/oauth/userinfo", ignSignInAndCsrf, auth.InfoOAuth)
m.Methods("POST, OPTIONS", "/login/oauth/access_token", optionsCorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth) m.Options("/login/oauth/access_token", CorsHandler(), misc.DummyBadRequest)
m.Methods("GET, OPTIONS", "/login/oauth/keys", optionsCorsHandler(), ignSignInAndCsrf, auth.OIDCKeys) m.Post("/login/oauth/access_token", CorsHandler(), web.Bind(forms.AccessTokenForm{}), ignSignInAndCsrf, auth.AccessTokenOAuth)
m.Methods("POST, OPTIONS", "/login/oauth/introspect", optionsCorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth) m.Options("/login/oauth/keys", CorsHandler(), misc.DummyBadRequest)
m.Get("/login/oauth/keys", ignSignInAndCsrf, auth.OIDCKeys)
m.Options("/login/oauth/introspect", CorsHandler(), misc.DummyBadRequest)
m.Post("/login/oauth/introspect", CorsHandler(), web.Bind(forms.IntrospectTokenForm{}), ignSignInAndCsrf, auth.IntrospectOAuth)
m.Group("/user/settings", func() { m.Group("/user/settings", func() {
m.Get("", user_setting.Profile) m.Get("", user_setting.Profile)
@ -780,7 +768,7 @@ func registerRoutes(m *web.Route) {
m.Group("", func() { m.Group("", func() {
m.Get("/{username}", user.UsernameSubRoute) m.Get("/{username}", user.UsernameSubRoute)
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment) m.Get("/attachments/{uuid}", repo.GetAttachment)
}, ignSignIn) }, ignSignIn)
m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action) m.Post("/{username}", reqSignIn, context_service.UserAssignmentWeb(), user.Action)

View File

@ -7,88 +7,17 @@ import (
"net/http" "net/http"
"testing" "testing"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/routers"
"code.gitea.io/gitea/tests" "code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestCORS(t *testing.T) { func TestCORSNotSet(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()
t.Run("CORS enabled", func(t *testing.T) { req := NewRequestf(t, "GET", "/api/v1/version")
defer test.MockVariableValue(&setting.CORSConfig.Enabled, true)() session := loginUser(t, "user2")
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())() resp := session.MakeRequest(t, req, http.StatusOK)
assert.Equal(t, resp.Code, http.StatusOK)
t.Run("API with CORS", func(t *testing.T) { corsHeader := resp.Header().Get("Access-Control-Allow-Origin")
// GET api with no CORS header assert.Empty(t, corsHeader, "Access-Control-Allow-Origin: generated header should match") // header not set
req := NewRequest(t, "GET", "/api/v1/version")
resp := MakeRequest(t, req, http.StatusOK)
assert.Empty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.Contains(t, resp.Header().Values("Vary"), "Origin")
// OPTIONS api for CORS
req = NewRequest(t, "OPTIONS", "/api/v1/version")
req.Header.Set("Origin", "https://example.com")
req.Header.Set("Access-Control-Request-Method", "GET")
resp = MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.Contains(t, resp.Header().Values("Vary"), "Origin")
})
t.Run("Web with CORS", func(t *testing.T) {
// GET userinfo with no CORS header
req := NewRequest(t, "GET", "/login/oauth/userinfo")
resp := MakeRequest(t, req, http.StatusUnauthorized)
assert.Empty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.Contains(t, resp.Header().Values("Vary"), "Origin")
// OPTIONS userinfo for CORS
req = NewRequest(t, "OPTIONS", "/login/oauth/userinfo")
req.Header.Set("Origin", "https://example.com")
req.Header.Set("Access-Control-Request-Method", "GET")
resp = MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.Contains(t, resp.Header().Values("Vary"), "Origin")
// OPTIONS userinfo for non-CORS
req = NewRequest(t, "OPTIONS", "/login/oauth/userinfo")
resp = MakeRequest(t, req, http.StatusMethodNotAllowed)
assert.NotContains(t, resp.Header().Values("Vary"), "Origin")
})
})
t.Run("CORS disabled", func(t *testing.T) {
defer test.MockVariableValue(&setting.CORSConfig.Enabled, false)()
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
t.Run("API without CORS", func(t *testing.T) {
req := NewRequest(t, "GET", "/api/v1/version")
resp := MakeRequest(t, req, http.StatusOK)
assert.Empty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.Empty(t, resp.Header().Values("Vary"))
req = NewRequest(t, "OPTIONS", "/api/v1/version")
req.Header.Set("Origin", "https://example.com")
req.Header.Set("Access-Control-Request-Method", "GET")
resp = MakeRequest(t, req, http.StatusMethodNotAllowed)
assert.Empty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.Empty(t, resp.Header().Values("Vary"))
})
t.Run("Web without CORS", func(t *testing.T) {
req := NewRequest(t, "GET", "/login/oauth/userinfo")
resp := MakeRequest(t, req, http.StatusUnauthorized)
assert.Empty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.NotContains(t, resp.Header().Values("Vary"), "Origin")
req = NewRequest(t, "OPTIONS", "/login/oauth/userinfo")
req.Header.Set("Origin", "https://example.com")
req.Header.Set("Access-Control-Request-Method", "GET")
resp = MakeRequest(t, req, http.StatusMethodNotAllowed)
assert.Empty(t, resp.Header().Get("Access-Control-Allow-Origin"))
assert.NotContains(t, resp.Header().Values("Vary"), "Origin")
})
})
} }