From a2b2e3066bee46ca15ce66d0deb7ef3e89915248 Mon Sep 17 00:00:00 2001
From: Earl Warren <contact@earl-warren.org>
Date: Tue, 27 Jun 2023 11:50:09 +0200
Subject: [PATCH] [TESTS] auth LinkAccount test coverage (squash)

---
 models/auth/source.go                 |  12 +++
 tests/integration/integration_test.go |  42 +++++++++
 tests/integration/linkaccount_test.go | 118 +++++++++++++++++++++-----
 3 files changed, 152 insertions(+), 20 deletions(-)

diff --git a/models/auth/source.go b/models/auth/source.go
index 0a904b7772..8dd1cd4759 100644
--- a/models/auth/source.go
+++ b/models/auth/source.go
@@ -5,6 +5,7 @@
 package auth
 
 import (
+	"context"
 	"fmt"
 	"reflect"
 
@@ -306,6 +307,17 @@ func GetSourceByID(id int64) (*Source, error) {
 	return source, nil
 }
 
+func GetSourceByName(ctx context.Context, name string) (*Source, error) {
+	source := &Source{}
+	has, err := db.GetEngine(ctx).Where("name = ?", name).Get(source)
+	if err != nil {
+		return nil, err
+	} else if !has {
+		return nil, ErrSourceNotExist{}
+	}
+	return source, nil
+}
+
 // UpdateSource updates a Source record in DB.
 func UpdateSource(source *Source) error {
 	var originalSource *Source
diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go
index 025325e31d..ce70eac733 100644
--- a/tests/integration/integration_test.go
+++ b/tests/integration/integration_test.go
@@ -35,10 +35,12 @@ import (
 	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/modules/web"
 	"code.gitea.io/gitea/routers"
+	"code.gitea.io/gitea/services/auth/source/oauth2"
 	user_service "code.gitea.io/gitea/services/user"
 	"code.gitea.io/gitea/tests"
 
 	"github.com/PuerkitoBio/goquery"
+	goth_gitlab "github.com/markbates/goth/providers/gitlab"
 	"github.com/stretchr/testify/assert"
 	"github.com/xeipuuv/gojsonschema"
 )
@@ -242,6 +244,46 @@ func getUserToken(t testing.TB, userName string, scope ...auth.AccessTokenScope)
 	return getTokenForLoggedInUser(t, loginUser(t, userName), scope...)
 }
 
+func addAuthSource(t *testing.T, payload map[string]string) *auth.Source {
+	session := loginUser(t, "user1")
+	payload["_csrf"] = GetCSRF(t, session, "/admin/auths/new")
+	req := NewRequestWithValues(t, "POST", "/admin/auths/new", payload)
+	session.MakeRequest(t, req, http.StatusSeeOther)
+	source, err := auth.GetSourceByName(context.Background(), payload["name"])
+	assert.NoError(t, err)
+	return source
+}
+
+func authSourcePayloadOAuth2(name string) map[string]string {
+	return map[string]string{
+		"type":      fmt.Sprintf("%d", auth.OAuth2),
+		"name":      name,
+		"is_active": "on",
+	}
+}
+
+func authSourcePayloadGitLab(name string) map[string]string {
+	payload := authSourcePayloadOAuth2(name)
+	payload["oauth2_provider"] = "gitlab"
+	return payload
+}
+
+func authSourcePayloadGitLabCustom(name string) map[string]string {
+	payload := authSourcePayloadGitLab(name)
+	payload["oauth2_use_custom_url"] = "on"
+	payload["oauth2_auth_url"] = goth_gitlab.AuthURL
+	payload["oauth2_token_url"] = goth_gitlab.TokenURL
+	payload["oauth2_profile_url"] = goth_gitlab.ProfileURL
+	return payload
+}
+
+func authSourcePayloadOIDC(name string) map[string]string {
+	payload := authSourcePayloadOAuth2(name)
+	payload["oauth2_provider"] = (&oauth2.OpenIDProvider{}).Name()
+	payload["open_id_connect_auto_discovery_url"] = codebergURL + "/.well-known/openid-configuration"
+	return payload
+}
+
 func createUser(ctx context.Context, t testing.TB, user *user_model.User) func() {
 	user.MustChangePassword = false
 	user.LowerName = strings.ToLower(user.Name)
diff --git a/tests/integration/linkaccount_test.go b/tests/integration/linkaccount_test.go
index e7b9f9c261..a951d79a60 100644
--- a/tests/integration/linkaccount_test.go
+++ b/tests/integration/linkaccount_test.go
@@ -1,12 +1,15 @@
-// Copyright 2023 The Forgejo Authors. All rights reserved.
+// SPDX-FileCopyrightText: Copyright the Forgejo contributors
 // SPDX-License-Identifier: MIT
 
 package integration
 
 import (
+	"context"
 	"net/http"
 	"testing"
 
+	auth_model "code.gitea.io/gitea/models/auth"
+	user_model "code.gitea.io/gitea/models/user"
 	gitea_context "code.gitea.io/gitea/modules/context"
 	"code.gitea.io/gitea/tests"
 
@@ -14,50 +17,125 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
+const codebergURL = "https://codeberg.org"
+
 func TestLinkAccountChoose(t *testing.T) {
 	defer tests.PrepareTestEnv(t)()
-	username := "linkaccountuser"
-	email := "linkaccountuser@example.com"
-	password := "linkaccountuser"
-	defer createUser(t, username, email, password)()
+	ctx := context.Background()
+
+	// Create a OIDC source and a known OAuth2 source
+	codebergName := "codeberg"
+	codeberg := addAuthSource(t, authSourcePayloadOIDC(codebergName))
+	gitlabName := "gitlab"
+	gitlab := addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
+
+	//
+	// A local user
+	//
+	localUser := &user_model.User{
+		Name:   "linkaccountuser",
+		Email:  "linkaccountuser@example.com",
+		Passwd: "linkaccountuser",
+		Type:   user_model.UserTypeIndividual,
+	}
+	defer createUser(ctx, t, localUser)()
+
+	//
+	// A Codeberg user via OIDC
+	//
+	userCodebergUserID := "1234"
+	userCodeberg := &user_model.User{
+		Name:        "linkaccountcodeberguser",
+		Email:       "linkaccountcodeberguser@example.com",
+		Passwd:      "linkaccountcodeberguser",
+		Type:        user_model.UserTypeIndividual,
+		LoginType:   auth_model.OAuth2,
+		LoginSource: codeberg.ID,
+		LoginName:   userCodebergUserID,
+	}
+	defer createUser(ctx, t, userCodeberg)()
+
+	//
+	// A Gitlab user
+	//
+	userGitLabUserID := "5678"
+	userGitLab := &user_model.User{
+		Name:        "linkaccountgitlabuser",
+		Email:       "linkaccountgitlabuser@example.com",
+		Passwd:      "linkaccountgitlabuser",
+		Type:        user_model.UserTypeIndividual,
+		LoginType:   auth_model.OAuth2,
+		LoginSource: gitlab.ID,
+		LoginName:   userGitLabUserID,
+	}
+	defer createUser(ctx, t, userGitLab)()
 
 	defer func() {
 		testMiddlewareHook = nil
 	}()
 
 	for _, testCase := range []struct {
+		title     string
 		gothUser  goth.User
 		signupTab string
 		signinTab string
 	}{
 		{
-			gothUser:  goth.User{},
+			title: "No existing user",
+			gothUser: goth.User{
+				Provider: codebergName,
+			},
 			signupTab: "item active",
 			signinTab: "item ",
 		},
 		{
+			title: "Matched local user",
 			gothUser: goth.User{
-				Email: email,
+				Provider: codebergName,
+				Email:    localUser.Email,
+			},
+			signupTab: "item ",
+			signinTab: "item active",
+		},
+		{
+			title: "Matched Codeberg local user",
+			gothUser: goth.User{
+				Provider: codebergName,
+				UserID:   userCodebergUserID,
+				Email:    userCodeberg.Email,
+			},
+			signupTab: "item ",
+			signinTab: "item active",
+		},
+		{
+			title: "Matched GitLab local user",
+			gothUser: goth.User{
+				Provider: gitlabName,
+				UserID:   userGitLabUserID,
+				Email:    userGitLab.Email,
 			},
 			signupTab: "item ",
 			signinTab: "item active",
 		},
 	} {
-		testMiddlewareHook = func(ctx *gitea_context.Context) {
-			ctx.Session.Set("linkAccountGothUser", testCase.gothUser)
-		}
+		t.Run(testCase.title, func(t *testing.T) {
+			testMiddlewareHook = func(ctx *gitea_context.Context) {
+				ctx.Session.Set("linkAccountGothUser", testCase.gothUser)
+			}
 
-		req := NewRequest(t, "GET", "/user/link_account")
-		resp := MakeRequest(t, req, http.StatusOK)
-		assert.Equal(t, resp.Code, http.StatusOK, resp.Body)
-		doc := NewHTMLParser(t, resp.Body)
+			req := NewRequest(t, "GET", "/user/link_account")
+			resp := MakeRequest(t, req, http.StatusOK)
+			if assert.Equal(t, resp.Code, http.StatusOK, resp.Body) {
+				doc := NewHTMLParser(t, resp.Body)
 
-		class, exists := doc.Find(`.new-menu-inner .item[data-tab="auth-link-signup-tab"]`).Attr("class")
-		assert.True(t, exists, resp.Body)
-		assert.Equal(t, testCase.signupTab, class)
+				class, exists := doc.Find(`.new-menu-inner .item[data-tab="auth-link-signup-tab"]`).Attr("class")
+				assert.True(t, exists, resp.Body)
+				assert.Equal(t, testCase.signupTab, class)
 
-		class, exists = doc.Find(`.new-menu-inner .item[data-tab="auth-link-signin-tab"]`).Attr("class")
-		assert.True(t, exists)
-		assert.Equal(t, testCase.signinTab, class)
+				class, exists = doc.Find(`.new-menu-inner .item[data-tab="auth-link-signin-tab"]`).Attr("class")
+				assert.True(t, exists)
+				assert.Equal(t, testCase.signinTab, class)
+			}
+		})
 	}
 }