From 3be80a863b6ef3671605a20800d8e2122d758ec5 Mon Sep 17 00:00:00 2001
From: wxiaoguang <wxiaoguang@gmail.com>
Date: Mon, 21 Aug 2023 12:15:55 +0800
Subject: [PATCH] Ignore the trailing slashes when comparing oauth2
 redirect_uri (#26597)

Fix #26526
---
 models/auth/oauth2.go      | 13 +++++++++++--
 models/auth/oauth2_test.go | 12 ++++++++++++
 2 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go
index 1b6d68879a..9c419eff69 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -132,6 +132,15 @@ func (app *OAuth2Application) TableName() string {
 
 // ContainsRedirectURI checks if redirectURI is allowed for app
 func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
+	contains := func(s string) bool {
+		s = strings.TrimSuffix(strings.ToLower(s), "/")
+		for _, u := range app.RedirectURIs {
+			if strings.TrimSuffix(strings.ToLower(u), "/") == s {
+				return true
+			}
+		}
+		return false
+	}
 	if !app.ConfidentialClient {
 		uri, err := url.Parse(redirectURI)
 		// ignore port for http loopback uris following https://datatracker.ietf.org/doc/html/rfc8252#section-7.3
@@ -140,13 +149,13 @@ func (app *OAuth2Application) ContainsRedirectURI(redirectURI string) bool {
 			if ip != nil && ip.IsLoopback() {
 				// strip port
 				uri.Host = uri.Hostname()
-				if util.SliceContainsString(app.RedirectURIs, uri.String(), true) {
+				if contains(uri.String()) {
 					return true
 				}
 			}
 		}
 	}
-	return util.SliceContainsString(app.RedirectURIs, redirectURI, true)
+	return contains(redirectURI)
 }
 
 // Base32 characters, but lowercased.
diff --git a/models/auth/oauth2_test.go b/models/auth/oauth2_test.go
index 80d0e9baa4..b8f0bc12c6 100644
--- a/models/auth/oauth2_test.go
+++ b/models/auth/oauth2_test.go
@@ -63,6 +63,18 @@ func TestOAuth2Application_ContainsRedirectURI_WithPort(t *testing.T) {
 	assert.False(t, app.ContainsRedirectURI(":"))
 }
 
+func TestOAuth2Application_ContainsRedirect_Slash(t *testing.T) {
+	app := &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1"}}
+	assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
+	assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
+	assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
+
+	app = &auth_model.OAuth2Application{RedirectURIs: []string{"http://127.0.0.1/"}}
+	assert.True(t, app.ContainsRedirectURI("http://127.0.0.1"))
+	assert.True(t, app.ContainsRedirectURI("http://127.0.0.1/"))
+	assert.False(t, app.ContainsRedirectURI("http://127.0.0.1/other"))
+}
+
 func TestOAuth2Application_ValidateClientSecret(t *testing.T) {
 	assert.NoError(t, unittest.PrepareTestDatabase())
 	app := unittest.AssertExistsAndLoadBean(t, &auth_model.OAuth2Application{ID: 1})