snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
141
oryx/corsx/check_origin.go
Normal file
141
oryx/corsx/check_origin.go
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package corsx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// CheckOrigin is a function that can be used well with cors.Options.AllowOriginRequestFunc.
|
||||
// It checks whether the origin is allowed following the same behavior as github.com/rs/cors.
|
||||
//
|
||||
// When legacyAllowInsecureOrigins is false (the default), wildcard patterns are
|
||||
// only honored when ClassifyOrigin reports them as bounded at a registrable
|
||||
// domain. Pass true to opt into legacy (trusting) behavior for unbounded
|
||||
// wildcards.
|
||||
//
|
||||
// TODO: legacyAllowInsecureOrigins grandfathers a fixed set of projects through
|
||||
// a time-boxed migration window (feature_flags.legacy_allow_insecure_origins).
|
||||
// Once those projects move to bounded wildcards and the entitlement is revoked,
|
||||
// drop this parameter and always enforce the boundary.
|
||||
//
|
||||
// Recommended usage for hot-reloadable origins:
|
||||
//
|
||||
// func (p *Config) cors(ctx context.Context, prefix string) (cors.Options, bool) {
|
||||
// opts, enabled := p.GetProvider(ctx).CORS(prefix, cors.Options{
|
||||
// AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
|
||||
// AllowedHeaders: []string{"Authorization", "Content-Type", "Cookie"},
|
||||
// ExposedHeaders: []string{"Content-Type", "Set-Cookie"},
|
||||
// AllowCredentials: true,
|
||||
// })
|
||||
// opts.AllowOriginRequestFunc = func(r *http.Request, origin string) bool {
|
||||
// // load the origins from the config on every request to allow hot-reloading
|
||||
// allowedOrigins := p.GetProvider(r.Context()).Strings(prefix + ".cors.allowed_origins")
|
||||
// return corsx.CheckOrigin(allowedOrigins, origin, false)
|
||||
// }
|
||||
// return opts, enabled
|
||||
// }
|
||||
func CheckOrigin(allowedOrigins []string, origin string, legacyAllowInsecureOrigins bool) bool {
|
||||
if len(allowedOrigins) == 0 {
|
||||
return true
|
||||
}
|
||||
for _, o := range allowedOrigins {
|
||||
if o == "*" {
|
||||
// allow all origins
|
||||
return true
|
||||
}
|
||||
// Note: for origins and methods matching, the spec requires a case-sensitive matching.
|
||||
// As it may be error-prone, we chose to ignore the spec here.
|
||||
// https://github.com/rs/cors/blob/066574eebbd0f5f1b6cd1154a160cc292ac1835e/cors.go#L132-L133
|
||||
o = strings.ToLower(o)
|
||||
prefix, suffix, found := strings.Cut(o, "*")
|
||||
if !found {
|
||||
// not a pattern, check for equality
|
||||
if o == origin {
|
||||
return true
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Only honor wildcards bounded at a registrable domain unless the caller
|
||||
// explicitly opts into legacy insecure matching. See ClassifyOrigin.
|
||||
if !legacyAllowInsecureOrigins && ClassifyOrigin(o).IsUnsafeWildcard() {
|
||||
continue
|
||||
}
|
||||
// inspired by https://github.com/rs/cors/blob/066574eebbd0f5f1b6cd1154a160cc292ac1835e/utils.go#L15
|
||||
if len(origin) >= len(prefix)+len(suffix) && strings.HasPrefix(origin, prefix) && strings.HasSuffix(origin, suffix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OriginPattern describes a CORS origin or return-URL host pattern: whether it
|
||||
// uses a wildcard and, if so, whether that wildcard is safely bounded at a
|
||||
// registrable domain.
|
||||
type OriginPattern struct {
|
||||
// HasWildcard reports whether the pattern contains a "*".
|
||||
HasWildcard bool
|
||||
|
||||
// BoundedWildcard reports whether the "*" is confined to a subdomain label
|
||||
// and the fixed domain that follows it is a registrable domain (an eTLD+1,
|
||||
// e.g. "example.com" or "example.co.uk"). Every host the pattern can match
|
||||
// then shares that one customer-owned registrable domain, so an attacker
|
||||
// cannot register a matching host. Always false when HasWildcard is false.
|
||||
BoundedWildcard bool
|
||||
|
||||
// Base is the fixed domain that follows the wildcard label — "example.com"
|
||||
// for "*.example.com", "com" for "*.com". It is empty for non-wildcards and
|
||||
// for bare or trailing wildcards where no domain follows the "*". When
|
||||
// BoundedWildcard is false, Base names the offending suffix, which is the
|
||||
// actionable signal for reporting why a wildcard was rejected.
|
||||
Base string
|
||||
}
|
||||
|
||||
// IsUnsafeWildcard reports whether the pattern is a wildcard that is NOT bounded
|
||||
// at a registrable domain. Such a wildcard would match an attacker-registrable
|
||||
// host (e.g. "https://*foo.com" matches "https://evilfoo.com"), so it must be
|
||||
// rejected unless the caller explicitly opts into legacy insecure matching. This
|
||||
// is the dominant question at call sites that gate, drop, or reject wildcard
|
||||
// origins and return URLs.
|
||||
func (p OriginPattern) IsUnsafeWildcard() bool {
|
||||
return p.HasWildcard && !p.BoundedWildcard
|
||||
}
|
||||
|
||||
// ClassifyOrigin inspects a CORS origin or bare host pattern and reports whether
|
||||
// it is a wildcard and, if so, whether the wildcard is safely bounded at a
|
||||
// registrable domain. Only the text from the last "*" onward is inspected, so
|
||||
// the result is identical whether pattern carries a scheme or is a bare host; a
|
||||
// trailing ":port" is ignored. Examples:
|
||||
//
|
||||
// - "https://*.example.com" → {HasWildcard: true, BoundedWildcard: true, Base: "example.com"}
|
||||
// - "https://*foo.com" → {HasWildcard: true, Base: "com"} (dot-less; base is a public suffix)
|
||||
// - "https://*.com" → {HasWildcard: true, Base: "com"} (public suffix, not registrable)
|
||||
// - "https://www.ory.*" → {HasWildcard: true} (trailing; no domain follows)
|
||||
// - "https://exact.foo.com" → {} (no wildcard)
|
||||
func ClassifyOrigin(pattern string) OriginPattern {
|
||||
i := strings.LastIndexByte(pattern, '*')
|
||||
if i < 0 {
|
||||
return OriginPattern{}
|
||||
}
|
||||
p := OriginPattern{HasWildcard: true}
|
||||
// The fixed domain is everything after the first "." that follows the last
|
||||
// "*" — i.e. the label containing the wildcard is dropped. Without such a dot
|
||||
// the wildcard is trailing or bare, so no registrable domain follows.
|
||||
_, base, found := strings.Cut(pattern[i:], ".")
|
||||
if !found {
|
||||
return p
|
||||
}
|
||||
base = strings.ToLower(base)
|
||||
if host, _, found := strings.Cut(base, ":"); found {
|
||||
base = host // Drop a trailing ":port".
|
||||
}
|
||||
p.Base = base
|
||||
// EffectiveTLDPlusOne returns an error when base is itself a public suffix
|
||||
// (e.g. "com", "co.uk", "vercel.app") or otherwise has no registrable domain.
|
||||
_, err := publicsuffix.EffectiveTLDPlusOne(base)
|
||||
p.BoundedWildcard = err == nil
|
||||
return p
|
||||
}
|
||||
46
oryx/corsx/cmd.go
Normal file
46
oryx/corsx/cmd.go
Normal file
@@ -0,0 +1,46 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package corsx
|
||||
|
||||
// HelpMessage returns a string containing information on setting up this CORS middleware.
|
||||
func HelpMessage() string {
|
||||
return `- CORS_ENABLED: Switch CORS support on (true) or off (false). Default is off (false).
|
||||
|
||||
Example: CORS_ENABLED=true
|
||||
|
||||
- CORS_ALLOWED_ORIGINS: A list of origins (comma separated values) a cross-domain request can be executed from.
|
||||
If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*)
|
||||
to replace 0 or more characters (i.e.: http://*.domain.com). Usage of wildcards implies a small performance penality.
|
||||
Only one wildcard can be used per origin. The default value is *.
|
||||
|
||||
Example: CORS_ALLOWED_ORIGINS=http://*.domain.com,http://*.domain2.com
|
||||
|
||||
- CORS_ALLOWED_METHODS: A list of methods (comma separated values) the client is allowed to use with cross-domain
|
||||
requests. Default value is simple methods (GET and POST).
|
||||
|
||||
Example: CORS_ALLOWED_METHODS=POST,GET,PUT
|
||||
|
||||
- CORS_ALLOWED_CREDENTIALS: Indicates whether the request can include user credentials like cookies, HTTP authentication
|
||||
or client side SSL certificates.
|
||||
|
||||
Default: CORS_ALLOWED_CREDENTIALS=false
|
||||
Example: CORS_ALLOWED_CREDENTIALS=true
|
||||
|
||||
- CORS_DEBUG: Debugging flag adds additional output to debug server side CORS issues.
|
||||
|
||||
Default: CORS_DEBUG=false
|
||||
Example: CORS_DEBUG=true
|
||||
|
||||
- CORS_MAX_AGE: Indicates how long (in seconds) the results of a preflight request can be cached. The default is 0
|
||||
which stands for no max age.
|
||||
|
||||
Default: CORS_MAX_AGE=0
|
||||
Example: CORS_MAX_AGE=10
|
||||
|
||||
- CORS_ALLOWED_HEADERS: A list of non simple headers (comma separated values) the client is allowed to use with
|
||||
cross-domain requests.
|
||||
|
||||
- CORS_EXPOSED_HEADERS: Indicates which headers (comma separated values) are safe to expose to the API of a
|
||||
CORS API specification.`
|
||||
}
|
||||
33
oryx/corsx/defaults.go
Normal file
33
oryx/corsx/defaults.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package corsx
|
||||
|
||||
// CORSRequestHeadersSafelist We add the safe list cors accept headers
|
||||
// https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_request_header
|
||||
var CORSRequestHeadersSafelist = []string{"Accept", "Content-Type", "Content-Length", "Accept-Language", "Content-Language"}
|
||||
|
||||
// CORSResponseHeadersSafelist We add the safe list cors expose headers
|
||||
// https://developer.mozilla.org/en-US/docs/Glossary/CORS-safelisted_response_header
|
||||
var CORSResponseHeadersSafelist = []string{"Set-Cookie", "Cache-Control", "Expires", "Last-Modified", "Pragma", "Content-Length", "Content-Language", "Content-Type"}
|
||||
|
||||
// CORSDefaultAllowedMethods Default allowed methods
|
||||
var CORSDefaultAllowedMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE"}
|
||||
|
||||
// CORSRequestHeadersExtended Extended list of request headers
|
||||
// these will be concatenated with the safelist
|
||||
var CORSRequestHeadersExtended = []string{"Authorization", "X-CSRF-TOKEN"}
|
||||
|
||||
// CORSResponseHeadersExtended Extended list of response headers
|
||||
// these will be concatenated with the safelist
|
||||
var CORSResponseHeadersExtended = []string{}
|
||||
|
||||
// CORSDefaultMaxAge max age for cache of preflight request result
|
||||
// default is 5 seconds
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
||||
var CORSDefaultMaxAge = 5
|
||||
|
||||
// CORSAllowCredentials default value for allow credentials
|
||||
// this is required for cookies to be sent by the browser
|
||||
// we always want this since we are using cookies for authentication most of the time
|
||||
var CORSAllowCredentials = true
|
||||
34
oryx/corsx/middleware.go
Normal file
34
oryx/corsx/middleware.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package corsx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/cors"
|
||||
"github.com/urfave/negroni"
|
||||
)
|
||||
|
||||
// ContextualizedMiddleware is a context-aware CORS middleware. It allows hot-reloading CORS configuration using
|
||||
// the HTTP request context.
|
||||
//
|
||||
// n := negroni.New()
|
||||
// n.UseFunc(ContextualizedMiddleware(func(context.Context) (opts cors.Options, enabled bool) {
|
||||
// panic("implement me")
|
||||
// })
|
||||
// // ...
|
||||
//
|
||||
// Deprecated: because this is not really practical to use, you should use CheckOrigin as the cors.Options.AllowOriginRequestFunc instead.
|
||||
func ContextualizedMiddleware(provider func(context.Context) (opts cors.Options, enabled bool)) negroni.HandlerFunc {
|
||||
return func(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
|
||||
options, enabled := provider(r.Context())
|
||||
if !enabled {
|
||||
next(rw, r)
|
||||
return
|
||||
}
|
||||
|
||||
cors.New(options).Handler(next).ServeHTTP(rw, r)
|
||||
}
|
||||
}
|
||||
28
oryx/corsx/normalize.go
Normal file
28
oryx/corsx/normalize.go
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package corsx
|
||||
|
||||
import "net/url"
|
||||
|
||||
// NormalizeOrigins normalizes the CORS origins.
|
||||
func NormalizeOrigins(origins []url.URL) []string {
|
||||
results := make([]string, len(origins))
|
||||
for k, o := range origins {
|
||||
results[k] = o.Scheme + "://" + o.Host
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
// NormalizeOriginStrings normalizes the CORS origins from string representation
|
||||
func NormalizeOriginStrings(origins []string) ([]string, error) {
|
||||
results := make([]string, len(origins))
|
||||
for k, o := range origins {
|
||||
u, err := url.ParseRequestURI(o)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results[k] = u.Scheme + "://" + u.Host
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
Reference in New Issue
Block a user