mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-04 07:55:31 +02:00
Fixes: #36846 1. When there is only on OAuth2 login method, automatically direct to it 2. Fix legacy problems in code, including: * Rename template filename and fix TODO comments * Fix legacy variable names * Add missing SSPI variable for template * Fix unnecessary layout, remove garbage styles * Only do AppUrl(ROOT_URL) check when it is needed (avoid unnecessary warnings to end users) --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
169 lines
6.6 KiB
Go
169 lines
6.6 KiB
Go
// Copyright 2024 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/url"
|
|
"testing"
|
|
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/session"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/test"
|
|
"code.gitea.io/gitea/modules/util"
|
|
"code.gitea.io/gitea/services/auth/source/oauth2"
|
|
"code.gitea.io/gitea/services/contexttest"
|
|
|
|
"github.com/markbates/goth"
|
|
"github.com/markbates/goth/gothic"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func addOAuth2Source(t *testing.T, authName string, cfg oauth2.Source) {
|
|
cfg.Provider = util.IfZero(cfg.Provider, "gitea")
|
|
err := auth_model.CreateSource(t.Context(), &auth_model.Source{
|
|
Type: auth_model.OAuth2,
|
|
Name: authName,
|
|
IsActive: true,
|
|
Cfg: &cfg,
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestWebAuthUserLogin(t *testing.T) {
|
|
ctx, resp := contexttest.MockContext(t, "/user/login")
|
|
SignIn(ctx)
|
|
assert.Equal(t, http.StatusOK, resp.Code)
|
|
|
|
ctx, resp = contexttest.MockContext(t, "/user/login")
|
|
ctx.IsSigned = true
|
|
SignIn(ctx)
|
|
assert.Equal(t, http.StatusSeeOther, resp.Code)
|
|
assert.Equal(t, "/", test.RedirectURL(resp))
|
|
|
|
ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to=/other")
|
|
ctx.IsSigned = true
|
|
SignIn(ctx)
|
|
assert.Equal(t, "/other", test.RedirectURL(resp))
|
|
|
|
ctx, resp = contexttest.MockContext(t, "/user/login")
|
|
ctx.Req.AddCookie(&http.Cookie{Name: "redirect_to", Value: "/other-cookie"})
|
|
ctx.IsSigned = true
|
|
SignIn(ctx)
|
|
assert.Equal(t, "/other-cookie", test.RedirectURL(resp))
|
|
|
|
ctx, resp = contexttest.MockContext(t, "/user/login?redirect_to="+url.QueryEscape("https://example.com"))
|
|
ctx.IsSigned = true
|
|
SignIn(ctx)
|
|
assert.Equal(t, "/", test.RedirectURL(resp))
|
|
}
|
|
|
|
func TestWebAuthOAuth2(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.OAuth2Client.EnableAutoRegistration, true)()
|
|
|
|
_ = oauth2.Init(t.Context())
|
|
addOAuth2Source(t, "dummy-auth-source", oauth2.Source{})
|
|
|
|
t.Run("OAuth2MissingField", func(t *testing.T) {
|
|
defer test.MockVariableValue(&gothic.CompleteUserAuth, func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
|
return goth.User{Provider: "dummy-auth-source", UserID: "dummy-user"}, nil
|
|
})()
|
|
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid")}
|
|
ctx, resp := contexttest.MockContext(t, "/user/oauth2/dummy-auth-source/callback?code=dummy-code", mockOpt)
|
|
ctx.SetPathParam("provider", "dummy-auth-source")
|
|
SignInOAuthCallback(ctx)
|
|
assert.Equal(t, http.StatusSeeOther, resp.Code)
|
|
assert.Equal(t, "/user/link_account", test.RedirectURL(resp))
|
|
|
|
// then the user will be redirected to the link account page, and see a message about the missing fields
|
|
ctx, _ = contexttest.MockContext(t, "/user/link_account", mockOpt)
|
|
LinkAccount(ctx)
|
|
assert.EqualValues(t, "auth.oauth_callback_unable_auto_reg:dummy-auth-source,email", ctx.Data["AutoRegistrationFailedPrompt"])
|
|
})
|
|
|
|
t.Run("OAuth2CallbackError", func(t *testing.T) {
|
|
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid")}
|
|
ctx, resp := contexttest.MockContext(t, "/user/oauth2/dummy-auth-source/callback", mockOpt)
|
|
ctx.SetPathParam("provider", "dummy-auth-source")
|
|
SignInOAuthCallback(ctx)
|
|
assert.Equal(t, http.StatusSeeOther, resp.Code)
|
|
assert.Equal(t, "/user/login", test.RedirectURL(resp))
|
|
assert.Contains(t, ctx.Flash.ErrorMsg, "auth.oauth.signin.error.general")
|
|
})
|
|
|
|
t.Run("RedirectSingleProvider", func(t *testing.T) {
|
|
enablePassword := &setting.Service.EnablePasswordSignInForm
|
|
enableOpenID := &setting.Service.EnableOpenIDSignIn
|
|
enablePasskey := &setting.Service.EnablePasskeyAuth
|
|
defer test.MockVariableValue(enablePassword, false)()
|
|
defer test.MockVariableValue(enableOpenID, false)()
|
|
defer test.MockVariableValue(enablePasskey, false)()
|
|
|
|
testSignIn := func(t *testing.T, link string, expectedCode int, expectedRedirect string) {
|
|
ctx, resp := contexttest.MockContext(t, link)
|
|
SignIn(ctx)
|
|
assert.Equal(t, expectedCode, resp.Code)
|
|
if expectedCode == http.StatusSeeOther {
|
|
assert.Equal(t, expectedRedirect, test.RedirectURL(resp))
|
|
}
|
|
}
|
|
testSignIn(t, "/user/login", http.StatusSeeOther, "/user/oauth2/dummy-auth-source")
|
|
testSignIn(t, "/user/login?redirect_to=/", http.StatusSeeOther, "/user/oauth2/dummy-auth-source?redirect_to=%2F")
|
|
|
|
*enablePassword, *enableOpenID, *enablePasskey = true, false, false
|
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
|
*enablePassword, *enableOpenID, *enablePasskey = false, true, false
|
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
|
*enablePassword, *enableOpenID, *enablePasskey = false, false, true
|
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
|
|
|
*enablePassword, *enableOpenID, *enablePasskey = false, false, false
|
|
addOAuth2Source(t, "dummy-auth-source-2", oauth2.Source{})
|
|
testSignIn(t, "/user/login", http.StatusOK, "")
|
|
})
|
|
|
|
t.Run("OIDCLogout", func(t *testing.T) {
|
|
var mockServer *httptest.Server
|
|
mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/.well-known/openid-configuration":
|
|
_, _ = w.Write([]byte(`{
|
|
"issuer": "` + mockServer.URL + `",
|
|
"authorization_endpoint": "` + mockServer.URL + `/authorize",
|
|
"token_endpoint": "` + mockServer.URL + `/token",
|
|
"userinfo_endpoint": "` + mockServer.URL + `/userinfo",
|
|
"end_session_endpoint": "https://example.com/oidc-logout?oidc-key=oidc-val"
|
|
}`))
|
|
default:
|
|
http.NotFound(w, r)
|
|
}
|
|
}))
|
|
defer mockServer.Close()
|
|
|
|
addOAuth2Source(t, "oidc-auth-source", oauth2.Source{
|
|
Provider: "openidConnect",
|
|
ClientID: "mock-client-id",
|
|
OpenIDConnectAutoDiscoveryURL: mockServer.URL + "/.well-known/openid-configuration",
|
|
})
|
|
authSource, err := auth_model.GetActiveOAuth2SourceByAuthName(t.Context(), "oidc-auth-source")
|
|
require.NoError(t, err)
|
|
|
|
mockOpt := contexttest.MockContextOption{SessionStore: session.NewMockMemStore("dummy-sid")}
|
|
ctx, resp := contexttest.MockContext(t, "/user/logout", mockOpt)
|
|
ctx.Doer = &user_model.User{ID: 1, LoginType: auth_model.OAuth2, LoginSource: authSource.ID}
|
|
SignOut(ctx)
|
|
assert.Equal(t, http.StatusSeeOther, resp.Code)
|
|
u, err := url.Parse(test.RedirectURL(resp))
|
|
require.NoError(t, err)
|
|
expectedValues := url.Values{"oidc-key": []string{"oidc-val"}, "post_logout_redirect_uri": []string{setting.AppURL}, "client_id": []string{"mock-client-id"}}
|
|
assert.Equal(t, expectedValues, u.Query())
|
|
u.RawQuery = ""
|
|
assert.Equal(t, "https://example.com/oidc-logout", u.String())
|
|
})
|
|
}
|