mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-02 14:12:27 +00:00
fix(oidc): accept localhost redirect URIs without path nor port (#10836)
# Which Problems Are Solved Some native OIDC applications use localhost without a path as redirect URI. Currently, setting `http://localhost` as a redirect URI leads to a compliance warning (`Redirect URIs must begin with your own protocol, http://127.0.0.1, http://[::1] or http://localhost.`), while `http://localhost/some/path` and `http://localhost:some-port` are accepted). # How the Problems Are Solved This PR adds additional checks to accept `http://localhost`, `http://127.0.0.1`, `http://[::1]` and `http://[0:0:0:0:0:0:0:1]` (their counterpart with port and with path were already accepted). --------- Co-authored-by: Marco Ardizzone <marco@zitadel.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"net/url"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -10,16 +12,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
http = "http://"
|
httpScheme = "http://"
|
||||||
httpLocalhostWithPort = "http://localhost:"
|
httpsScheme = "https://"
|
||||||
httpLocalhostWithoutPort = "http://localhost/"
|
localhostHostname = "localhost"
|
||||||
httpLoopbackV4WithPort = "http://127.0.0.1:"
|
|
||||||
httpLoopbackV4WithoutPort = "http://127.0.0.1/"
|
|
||||||
httpLoopbackV6WithPort = "http://[::1]:"
|
|
||||||
httpLoopbackV6WithoutPort = "http://[::1]/"
|
|
||||||
httpLoopbackV6LongWithPort = "http://[0:0:0:0:0:0:0:1]:"
|
|
||||||
httpLoopbackV6LongWithoutPort = "http://[0:0:0:0:0:0:0:1]/"
|
|
||||||
https = "https://"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type OIDCApp struct {
|
type OIDCApp struct {
|
||||||
@@ -297,7 +292,7 @@ func CheckRedirectUrisCode(compliance *Compliance, appType *OIDCApplicationType,
|
|||||||
if urlsAreHttps(redirectUris) {
|
if urlsAreHttps(redirectUris) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if urlContainsPrefix(redirectUris, http) {
|
if urlContainsPrefix(redirectUris, httpScheme) {
|
||||||
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
|
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
||||||
@@ -321,7 +316,7 @@ func CheckRedirectUrisImplicit(compliance *Compliance, appType *OIDCApplicationT
|
|||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
||||||
}
|
}
|
||||||
if urlContainsPrefix(redirectUris, http) {
|
if urlContainsPrefix(redirectUris, httpScheme) {
|
||||||
if appType != nil && *appType == OIDCApplicationTypeNative {
|
if appType != nil && *appType == OIDCApplicationTypeNative {
|
||||||
if !onlyLocalhostIsHttp(redirectUris) {
|
if !onlyLocalhostIsHttp(redirectUris) {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
@@ -342,7 +337,7 @@ func CheckRedirectUrisImplicitAndCode(compliance *Compliance, appType *OIDCAppli
|
|||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
||||||
}
|
}
|
||||||
if urlContainsPrefix(redirectUris, http) {
|
if urlContainsPrefix(redirectUris, httpScheme) {
|
||||||
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
|
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
||||||
@@ -359,7 +354,7 @@ func CheckRedirectUrisImplicitAndCode(compliance *Compliance, appType *OIDCAppli
|
|||||||
|
|
||||||
func urlsAreHttps(uris []string) bool {
|
func urlsAreHttps(uris []string) bool {
|
||||||
for _, uri := range uris {
|
for _, uri := range uris {
|
||||||
if !strings.HasPrefix(uri, https) {
|
if !strings.HasPrefix(uri, httpsScheme) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -377,33 +372,52 @@ func urlContainsPrefix(uris []string, prefix string) bool {
|
|||||||
|
|
||||||
func containsCustom(uris []string) bool {
|
func containsCustom(uris []string) bool {
|
||||||
for _, uri := range uris {
|
for _, uri := range uris {
|
||||||
if !strings.HasPrefix(uri, http) && !strings.HasPrefix(uri, https) {
|
if !strings.HasPrefix(uri, httpScheme) && !strings.HasPrefix(uri, httpsScheme) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// onlyLocalhostIsHttp returns true if:
|
||||||
|
//
|
||||||
|
// - input string slice is empty
|
||||||
|
// - all parseable URIs with scheme `http` in the string slice are localhost/loopback URIs (in all possible forms)
|
||||||
|
//
|
||||||
|
// It will return false if:
|
||||||
|
// - any of the input URIs cannot be parsed
|
||||||
|
// - any of the parseable input URIs with scheme `http` is not localhost/loopback
|
||||||
func onlyLocalhostIsHttp(uris []string) bool {
|
func onlyLocalhostIsHttp(uris []string) bool {
|
||||||
for _, uri := range uris {
|
for _, uri := range uris {
|
||||||
if strings.HasPrefix(uri, http) && !isHTTPLoopbackLocalhost(uri) {
|
url, err := url.ParseRequestURI(uri)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if url.Scheme == "http" {
|
||||||
|
hostname := url.Hostname()
|
||||||
|
|
||||||
|
if hostname == localhostHostname {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
address, err := netip.ParseAddr(hostname)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if address.IsLoopback() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func isHTTPLoopbackLocalhost(uri string) bool {
|
|
||||||
return strings.HasPrefix(uri, httpLocalhostWithoutPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLocalhostWithPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLoopbackV4WithoutPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLoopbackV4WithPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLoopbackV6WithoutPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLoopbackV6WithPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLoopbackV6LongWithoutPort) ||
|
|
||||||
strings.HasPrefix(uri, httpLoopbackV6LongWithPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
func OIDCOriginAllowList(redirectURIs, additionalOrigins []string) ([]string, error) {
|
func OIDCOriginAllowList(redirectURIs, additionalOrigins []string) ([]string, error) {
|
||||||
allowList := make([]string, 0)
|
allowList := make([]string, 0)
|
||||||
for _, redirect := range redirectURIs {
|
for _, redirect := range redirectURIs {
|
||||||
|
|||||||
@@ -489,7 +489,7 @@ func TestCheckRedirectUrisImplicit(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "only http protocol, app type native, only localhost",
|
name: "only http protocol, app type native, only localhost",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://localhost:8080"},
|
redirectUris: []string{"http://localhost:8080", "http://localhost/", "http://localhost"},
|
||||||
appType: gu.Ptr(OIDCApplicationTypeNative),
|
appType: gu.Ptr(OIDCApplicationTypeNative),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
|
|||||||
Reference in New Issue
Block a user