From 4aed0e6b074b538ba71ba0560a416f282b3cc30b Mon Sep 17 00:00:00 2001
From: Giteabot <teabot@gitea.io>
Date: Mon, 21 Aug 2023 14:19:43 +0800
Subject: [PATCH] Ignore the trailing slashes when comparing oauth2
 redirect_uri (#26597) (#26618)

Backport #26597 by @wxiaoguang

Fix #26526

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
---
 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 53a5c28b4a..01ec23a5a6 100644
--- a/models/auth/oauth2.go
+++ b/models/auth/oauth2.go
@@ -53,6 +53,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
@@ -61,13 +70,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})