Repository: dexidp/dex
Pull request #3778 (2024-10-03 15:02:34)

feat: also allow localhost equivalent IP addresses
<!--
Thank you for sending a pull request! Here are some tips for contributors:

1. Fill the description template below.
2. Sign a DCO (if you haven't already signed it).
3. Include appropriate tests (if necessary). Make sure that all CI checks passed.
4. If the Pull Request is a work in progress, make use of GitHub's "Draft PR" feature and mark it as such.
-->

#### Overview

Instead of only checking for "localhost", also validate through `net.ParseIP` + `IsLoopback` whether the host is numerically localhost

#### What this PR does / why we need it

Currently, `localhost` is already used as a special case that allows any port and http scheme if no particular redirectURIs are configured. When integrating with ownClouds desktop client, it actually sends an arbitrary `http://127.0.0.1:xyz` redirect uri. Considering that the go standard library already has the functionality to check if a given IP is a loopback address, we can extend the `== "localhost"` with also a check for `IsLoopback()` which should allow the ownCloud desktop client to be accepted.

#### Special notes for your reviewer


nabokihms: Great improvement! Thanks.

Patch:
diff --git a/server/oauth2.go b/server/oauth2.go
index ec972bea..cc81a8a5 100644
--- a/server/oauth2.go
+++ b/server/oauth2.go
@@ -668,7 +668,8 @@ func validateRedirectURI(client storage.Client, redirectURI string) bool {
 		return true
 	}
 
-	// verify that the host is of form "http://localhost:(port)(path)" or "http://localhost(path)"
+	// verify that the host is of form "http://localhost:(port)(path)", "http://localhost(path)" or numeric form like
+	// "http://127.0.0.1:(port)(path)"
 	u, err := url.Parse(redirectURI)
 	if err != nil {
 		return false
@@ -676,11 +677,20 @@ func validateRedirectURI(client storage.Client, redirectURI string) bool {
 	if u.Scheme != "http" {
 		return false
 	}
-	if u.Host == "localhost" {
+	return isHostLocal(u.Host)
+}
+
+func isHostLocal(host string) bool {
+	if host == "localhost" || net.ParseIP(host).IsLoopback() {
 		return true
 	}
-	host, _, err := net.SplitHostPort(u.Host)
-	return err == nil && host == "localhost"
+
+	host, _, err := net.SplitHostPort(host)
+	if err != nil {
+		return false
+	}
+
+	return host == "localhost" || net.ParseIP(host).IsLoopback()
 }
 
 func validateConnectorID(connectors []storage.Connector, connectorID string) bool {


Test patch:
diff --git a/server/oauth2_test.go b/server/oauth2_test.go
index 5f5fc3b6..2b733df8 100644
--- a/server/oauth2_test.go
+++ b/server/oauth2_test.go
@@ -452,6 +452,27 @@ func TestValidRedirectURI(t *testing.T) {
 			redirectURI: "http://localhost",
 			wantValid:   true,
 		},
+		{
+			client: storage.Client{
+				Public: true,
+			},
+			redirectURI: "http://127.0.0.1:8080/",
+			wantValid:   true,
+		},
+		{
+			client: storage.Client{
+				Public: true,
+			},
+			redirectURI: "http://127.0.0.1:991/bar",
+			wantValid:   true,
+		},
+		{
+			client: storage.Client{
+				Public: true,
+			},
+			redirectURI: "http://127.0.0.1",
+			wantValid:   true,
+		},
 		// Both Public + RedirectURIs configured: Could e.g. be a PKCE-enabled web app.
 		{
 			client: storage.Client{