From d694579bdffacc720e8d80c36ea11252abeaebee Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Tue, 16 Jan 2024 14:11:28 +0000 Subject: [PATCH] Revert "[GITEA] rework long-term authentication" This reverts commit 8d2dab94a65d7ff5c88721c62f5996b8e2634b1e. --- models/auth/auth_token.go | 96 --------------- models/forgejo_migrations/migrate.go | 2 - models/forgejo_migrations/v1_20/v3.go | 25 ---- models/user/user.go | 5 - modules/context/context_cookie.go | 54 +++++++-- modules/setting/security.go | 2 + modules/util/legacy.go | 53 +++++++++ modules/util/legacy_test.go | 20 ++++ routers/install/install.go | 13 +- routers/web/auth/auth.go | 61 ++++------ routers/web/auth/oauth.go | 3 +- routers/web/home.go | 3 +- routers/web/user/setting/account.go | 9 -- routers/web/web.go | 2 +- services/auth/auth.go | 4 + tests/integration/auth_token_test.go | 163 -------------------------- tests/integration/integration_test.go | 8 -- 17 files changed, 156 insertions(+), 367 deletions(-) delete mode 100644 models/auth/auth_token.go delete mode 100644 models/forgejo_migrations/v1_20/v3.go delete mode 100644 tests/integration/auth_token_test.go diff --git a/models/auth/auth_token.go b/models/auth/auth_token.go deleted file mode 100644 index 2c3ca90734..0000000000 --- a/models/auth/auth_token.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2023 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package auth - -import ( - "context" - "crypto/sha256" - "encoding/hex" - "fmt" - "time" - - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/modules/util" -) - -// AuthorizationToken represents a authorization token to a user. -type AuthorizationToken struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX"` - LookupKey string `xorm:"INDEX UNIQUE"` - HashedValidator string - Expiry timeutil.TimeStamp -} - -// TableName provides the real table name. -func (AuthorizationToken) TableName() string { - return "forgejo_auth_token" -} - -func init() { - db.RegisterModel(new(AuthorizationToken)) -} - -// IsExpired returns if the authorization token is expired. -func (authToken *AuthorizationToken) IsExpired() bool { - return authToken.Expiry.AsLocalTime().Before(time.Now()) -} - -// GenerateAuthToken generates a new authentication token for the given user. -// It returns the lookup key and validator values that should be passed to the -// user via a long-term cookie. -func GenerateAuthToken(ctx context.Context, userID int64, expiry timeutil.TimeStamp) (lookupKey, validator string, err error) { - // Request 64 random bytes. The first 32 bytes will be used for the lookupKey - // and the other 32 bytes will be used for the validator. - rBytes, err := util.CryptoRandomBytes(64) - if err != nil { - return "", "", err - } - hexEncoded := hex.EncodeToString(rBytes) - validator, lookupKey = hexEncoded[64:], hexEncoded[:64] - - _, err = db.GetEngine(ctx).Insert(&AuthorizationToken{ - UID: userID, - Expiry: expiry, - LookupKey: lookupKey, - HashedValidator: HashValidator(rBytes[32:]), - }) - return lookupKey, validator, err -} - -// FindAuthToken will find a authorization token via the lookup key. -func FindAuthToken(ctx context.Context, lookupKey string) (*AuthorizationToken, error) { - var authToken AuthorizationToken - has, err := db.GetEngine(ctx).Where("lookup_key = ?", lookupKey).Get(&authToken) - if err != nil { - return nil, err - } else if !has { - return nil, fmt.Errorf("lookup key %q: %w", lookupKey, util.ErrNotExist) - } - return &authToken, nil -} - -// DeleteAuthToken will delete the authorization token. -func DeleteAuthToken(ctx context.Context, authToken *AuthorizationToken) error { - _, err := db.DeleteByBean(ctx, authToken) - return err -} - -// DeleteAuthTokenByUser will delete all authorization tokens for the user. -func DeleteAuthTokenByUser(ctx context.Context, userID int64) error { - if userID == 0 { - return nil - } - - _, err := db.DeleteByBean(ctx, &AuthorizationToken{UID: userID}) - return err -} - -// HashValidator will return a hexified hashed version of the validator. -func HashValidator(validator []byte) string { - h := sha256.New() - h.Write(validator) - return hex.EncodeToString(h.Sum(nil)) -} diff --git a/models/forgejo_migrations/migrate.go b/models/forgejo_migrations/migrate.go index 58f158bd17..2becf1b713 100644 --- a/models/forgejo_migrations/migrate.go +++ b/models/forgejo_migrations/migrate.go @@ -41,8 +41,6 @@ var migrations = []*Migration{ NewMigration("Add Forgejo Blocked Users table", forgejo_v1_20.AddForgejoBlockedUser), // v1 -> v2 NewMigration("create the forgejo_sem_ver table", forgejo_v1_20.CreateSemVerTable), - // v2 -> v3 - NewMigration("create the forgejo_auth_token table", forgejo_v1_20.CreateAuthorizationTokenTable), } // GetCurrentDBVersion returns the current Forgejo database version. diff --git a/models/forgejo_migrations/v1_20/v3.go b/models/forgejo_migrations/v1_20/v3.go deleted file mode 100644 index 38c29bed03..0000000000 --- a/models/forgejo_migrations/v1_20/v3.go +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT - -package forgejo_v1_20 //nolint:revive - -import ( - "code.gitea.io/gitea/modules/timeutil" - - "xorm.io/xorm" -) - -type AuthorizationToken struct { - ID int64 `xorm:"pk autoincr"` - UID int64 `xorm:"INDEX"` - LookupKey string `xorm:"INDEX UNIQUE"` - HashedValidator string - Expiry timeutil.TimeStamp -} - -func (AuthorizationToken) TableName() string { - return "forgejo_auth_token" -} - -func CreateAuthorizationTokenTable(x *xorm.Engine) error { - return x.Sync(new(AuthorizationToken)) -} diff --git a/models/user/user.go b/models/user/user.go index 851db07314..1254ef9326 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -386,11 +386,6 @@ func (u *User) SetPassword(passwd string) (err error) { return nil } - // Invalidate all authentication tokens for this user. - if err := auth.DeleteAuthTokenByUser(db.DefaultContext, u.ID); err != nil { - return err - } - if u.Salt, err = GetUserSalt(); err != nil { return err } diff --git a/modules/context/context_cookie.go b/modules/context/context_cookie.go index 39e3218d1b..1fd9719d31 100644 --- a/modules/context/context_cookie.go +++ b/modules/context/context_cookie.go @@ -4,14 +4,16 @@ package context import ( + "crypto/sha256" + "encoding/hex" "net/http" "strings" - auth_model "code.gitea.io/gitea/models/auth" - user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web/middleware" + + "golang.org/x/crypto/pbkdf2" ) const CookieNameFlash = "gitea_flash" @@ -44,13 +46,41 @@ func (ctx *Context) GetSiteCookie(name string) string { return middleware.GetSiteCookie(ctx.Req, name) } -// SetLTACookie will generate a LTA token and add it as an cookie. -func (ctx *Context) SetLTACookie(u *user_model.User) error { - days := 86400 * setting.LogInRememberDays - lookup, validator, err := auth_model.GenerateAuthToken(ctx, u.ID, timeutil.TimeStampNow().Add(int64(days))) - if err != nil { - return err - } - ctx.SetSiteCookie(setting.CookieRememberName, lookup+":"+validator, days) - return nil +// GetSuperSecureCookie returns given cookie value from request header with secret string. +func (ctx *Context) GetSuperSecureCookie(secret, name string) (string, bool) { + val := ctx.GetSiteCookie(name) + return ctx.CookieDecrypt(secret, val) +} + +// CookieDecrypt returns given value from with secret string. +func (ctx *Context) CookieDecrypt(secret, val string) (string, bool) { + if val == "" { + return "", false + } + + text, err := hex.DecodeString(val) + if err != nil { + return "", false + } + + key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New) + text, err = util.AESGCMDecrypt(key, text) + return string(text), err == nil +} + +// SetSuperSecureCookie sets given cookie value to response header with secret string. +func (ctx *Context) SetSuperSecureCookie(secret, name, value string, maxAge int) { + text := ctx.CookieEncrypt(secret, value) + ctx.SetSiteCookie(name, text, maxAge) +} + +// CookieEncrypt encrypts a given value using the provided secret +func (ctx *Context) CookieEncrypt(secret, value string) string { + key := pbkdf2.Key([]byte(secret), []byte(secret), 1000, 16, sha256.New) + text, err := util.AESGCMEncrypt(key, []byte(value)) + if err != nil { + panic("error encrypting cookie: " + err.Error()) + } + + return hex.EncodeToString(text) } diff --git a/modules/setting/security.go b/modules/setting/security.go index 92caa05fad..90f614d4cd 100644 --- a/modules/setting/security.go +++ b/modules/setting/security.go @@ -19,6 +19,7 @@ var ( SecretKey string InternalToken string // internal access token LogInRememberDays int + CookieUserName string CookieRememberName string ReverseProxyAuthUser string ReverseProxyAuthEmail string @@ -103,6 +104,7 @@ func loadSecurityFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("security") InstallLock = HasInstallLock(rootCfg) LogInRememberDays = sec.Key("LOGIN_REMEMBER_DAYS").MustInt(7) + CookieUserName = sec.Key("COOKIE_USERNAME").MustString("gitea_awesome") SecretKey = loadSecret(sec, "SECRET_KEY_URI", "SECRET_KEY") if SecretKey == "" { // FIXME: https://github.com/go-gitea/gitea/issues/16832 diff --git a/modules/util/legacy.go b/modules/util/legacy.go index 2d4de01949..2ea293a2be 100644 --- a/modules/util/legacy.go +++ b/modules/util/legacy.go @@ -4,6 +4,10 @@ package util import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "errors" "io" "os" ) @@ -36,3 +40,52 @@ func CopyFile(src, dest string) error { } return os.Chmod(dest, si.Mode()) } + +// AESGCMEncrypt (from legacy package): encrypts plaintext with the given key using AES in GCM mode. should be replaced. +func AESGCMEncrypt(key, plaintext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + nonce := make([]byte, gcm.NonceSize()) + if _, err := rand.Read(nonce); err != nil { + return nil, err + } + + ciphertext := gcm.Seal(nil, nonce, plaintext, nil) + return append(nonce, ciphertext...), nil +} + +// AESGCMDecrypt (from legacy package): decrypts ciphertext with the given key using AES in GCM mode. should be replaced. +func AESGCMDecrypt(key, ciphertext []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + gcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + size := gcm.NonceSize() + if len(ciphertext)-size <= 0 { + return nil, errors.New("ciphertext is empty") + } + + nonce := ciphertext[:size] + ciphertext = ciphertext[size:] + + plainText, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plainText, nil +} diff --git a/modules/util/legacy_test.go b/modules/util/legacy_test.go index b7991bd365..e732094c29 100644 --- a/modules/util/legacy_test.go +++ b/modules/util/legacy_test.go @@ -4,6 +4,8 @@ package util import ( + "crypto/aes" + "crypto/rand" "fmt" "os" "testing" @@ -35,3 +37,21 @@ func TestCopyFile(t *testing.T) { assert.NoError(t, err) assert.Equal(t, testContent, dstContent) } + +func TestAESGCM(t *testing.T) { + t.Parallel() + + key := make([]byte, aes.BlockSize) + _, err := rand.Read(key) + assert.NoError(t, err) + + plaintext := []byte("this will be encrypted") + + ciphertext, err := AESGCMEncrypt(key, plaintext) + assert.NoError(t, err) + + decrypted, err := AESGCMDecrypt(key, ciphertext) + assert.NoError(t, err) + + assert.Equal(t, plaintext, decrypted) +} diff --git a/routers/install/install.go b/routers/install/install.go index 648425df3b..80651c7e9b 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -553,16 +553,21 @@ func SubmitInstall(ctx *context.Context) { u, _ = user_model.GetUserByName(ctx, u.Name) } - if err := ctx.SetLTACookie(u); err != nil { - ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) - return - } + days := 86400 * setting.LogInRememberDays + ctx.SetSiteCookie(setting.CookieUserName, u.Name, days) + + ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), + setting.CookieRememberName, u.Name, days) // Auto-login for admin if err = ctx.Session.Set("uid", u.ID); err != nil { ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) return } + if err = ctx.Session.Set("uname", u.Name); err != nil { + ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) + return + } if err = ctx.Session.Release(); err != nil { ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form) diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 884c6fe266..465077a909 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -5,8 +5,6 @@ package auth import ( - "crypto/subtle" - "encoding/hex" "errors" "fmt" "net/http" @@ -52,47 +50,21 @@ func AutoSignIn(ctx *context.Context) (bool, error) { return false, nil } - authCookie := ctx.GetSiteCookie(setting.CookieRememberName) - if len(authCookie) == 0 { + uname := ctx.GetSiteCookie(setting.CookieUserName) + if len(uname) == 0 { return false, nil } isSucceed := false defer func() { if !isSucceed { - log.Trace("Auto login cookie is cleared: %s", authCookie) + log.Trace("auto-login cookie cleared: %s", uname) + ctx.DeleteSiteCookie(setting.CookieUserName) ctx.DeleteSiteCookie(setting.CookieRememberName) } }() - lookupKey, validator, found := strings.Cut(authCookie, ":") - if !found { - return false, nil - } - - authToken, err := auth.FindAuthToken(ctx, lookupKey) - if err != nil { - if errors.Is(err, util.ErrNotExist) { - return false, nil - } - return false, err - } - - if authToken.IsExpired() { - err = auth.DeleteAuthToken(ctx, authToken) - return false, err - } - - rawValidator, err := hex.DecodeString(validator) - if err != nil { - return false, err - } - - if subtle.ConstantTimeCompare([]byte(authToken.HashedValidator), []byte(auth.HashValidator(rawValidator))) == 0 { - return false, nil - } - - u, err := user_model.GetUserByID(ctx, authToken.UID) + u, err := user_model.GetUserByName(ctx, uname) if err != nil { if !user_model.IsErrUserNotExist(err) { return false, fmt.Errorf("GetUserByName: %w", err) @@ -100,11 +72,17 @@ func AutoSignIn(ctx *context.Context) (bool, error) { return false, nil } + if val, ok := ctx.GetSuperSecureCookie( + base.EncodeMD5(u.Rands+u.Passwd), setting.CookieRememberName); !ok || val != u.Name { + return false, nil + } + isSucceed = true if err := updateSession(ctx, nil, map[string]any{ // Set session IDs - "uid": authToken.UID, + "uid": u.ID, + "uname": u.Name, }); err != nil { return false, fmt.Errorf("unable to updateSession: %w", err) } @@ -313,10 +291,10 @@ func handleSignIn(ctx *context.Context, u *user_model.User, remember bool) { func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRedirect bool) string { if remember { - if err := ctx.SetLTACookie(u); err != nil { - ctx.ServerError("GenerateAuthToken", err) - return setting.AppSubURL + "/" - } + days := 86400 * setting.LogInRememberDays + ctx.SetSiteCookie(setting.CookieUserName, u.Name, days) + ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), + setting.CookieRememberName, u.Name, days) } if err := updateSession(ctx, []string{ @@ -329,7 +307,8 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe "twofaRemember", "linkAccount", }, map[string]any{ - "uid": u.ID, + "uid": u.ID, + "uname": u.Name, }); err != nil { ctx.ServerError("RegenerateSession", err) return setting.AppSubURL + "/" @@ -390,6 +369,7 @@ func getUserName(gothUser *goth.User) string { func HandleSignOut(ctx *context.Context) { _ = ctx.Session.Flush() _ = ctx.Session.Destroy(ctx.Resp, ctx.Req) + ctx.DeleteSiteCookie(setting.CookieUserName) ctx.DeleteSiteCookie(setting.CookieRememberName) ctx.Csrf.DeleteCookie(ctx) middleware.DeleteRedirectToCookie(ctx.Resp) @@ -752,7 +732,8 @@ func handleAccountActivation(ctx *context.Context, user *user_model.User) { log.Trace("User activated: %s", user.Name) if err := updateSession(ctx, nil, map[string]any{ - "uid": user.ID, + "uid": user.ID, + "uname": user.Name, }); err != nil { log.Error("Unable to regenerate session for user: %-v with email: %s: %v", user, user.Email, err) ctx.ServerError("ActivateUserEmail", err) diff --git a/routers/web/auth/oauth.go b/routers/web/auth/oauth.go index 6fa60282fe..1be9c630a2 100644 --- a/routers/web/auth/oauth.go +++ b/routers/web/auth/oauth.go @@ -1122,7 +1122,8 @@ func handleOAuth2SignIn(ctx *context.Context, source *auth.Source, u *user_model // we can't sign the user in just yet. Instead, redirect them to the 2FA authentication page. if !needs2FA { if err := updateSession(ctx, nil, map[string]any{ - "uid": u.ID, + "uid": u.ID, + "uname": u.Name, }); err != nil { ctx.ServerError("updateSession", err) return diff --git a/routers/web/home.go b/routers/web/home.go index 4bcc4adcbf..ab3fbde2c9 100644 --- a/routers/web/home.go +++ b/routers/web/home.go @@ -54,7 +54,8 @@ func Home(ctx *context.Context) { } // Check auto-login. - if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 { + uname := ctx.GetSiteCookie(setting.CookieUserName) + if len(uname) != 0 { ctx.Redirect(setting.AppSubURL + "/user/login") return } diff --git a/routers/web/user/setting/account.go b/routers/web/user/setting/account.go index f50c19a923..5c14f3ad4b 100644 --- a/routers/web/user/setting/account.go +++ b/routers/web/user/setting/account.go @@ -78,15 +78,6 @@ func AccountPost(ctx *context.Context) { ctx.ServerError("UpdateUser", err) return } - - // Re-generate LTA cookie. - if len(ctx.GetSiteCookie(setting.CookieRememberName)) != 0 { - if err := ctx.SetLTACookie(ctx.Doer); err != nil { - ctx.ServerError("SetLTACookie", err) - return - } - } - log.Trace("User password updated: %s", ctx.Doer.Name) ctx.Flash.Success(ctx.Tr("settings.change_password_success")) } diff --git a/routers/web/web.go b/routers/web/web.go index 9499363fa9..85cbd4669b 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -196,7 +196,7 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont // Redirect to log in page if auto-signin info is provided and has not signed in. if !options.SignOutRequired && !ctx.IsSigned && - len(ctx.GetSiteCookie(setting.CookieRememberName)) > 0 { + len(ctx.GetSiteCookie(setting.CookieUserName)) > 0 { if ctx.Req.URL.Path != "/user/events" { middleware.SetRedirectToCookie(ctx.Resp, setting.AppSubURL+ctx.Req.URL.RequestURI()) } diff --git a/services/auth/auth.go b/services/auth/auth.go index 4adf549204..713463a3d4 100644 --- a/services/auth/auth.go +++ b/services/auth/auth.go @@ -76,6 +76,10 @@ func handleSignIn(resp http.ResponseWriter, req *http.Request, sess SessionStore if err != nil { log.Error(fmt.Sprintf("Error setting session: %v", err)) } + err = sess.Set("uname", user.Name) + if err != nil { + log.Error(fmt.Sprintf("Error setting session: %v", err)) + } // Language setting of the user overwrites the one previously set // If the user does not have a locale set, we save the current one. diff --git a/tests/integration/auth_token_test.go b/tests/integration/auth_token_test.go deleted file mode 100644 index 24c66ee261..0000000000 --- a/tests/integration/auth_token_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright 2023 The Forgejo Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package integration - -import ( - "encoding/hex" - "net/http" - "net/url" - "strings" - "testing" - - "code.gitea.io/gitea/models/auth" - "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/models/unittest" - user_model "code.gitea.io/gitea/models/user" - "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/timeutil" - "code.gitea.io/gitea/tests" - - "github.com/stretchr/testify/assert" -) - -// GetSessionForLTACookie returns a new session with only the LTA cookie being set. -func GetSessionForLTACookie(t *testing.T, ltaCookie *http.Cookie) *TestSession { - t.Helper() - - ch := http.Header{} - ch.Add("Cookie", ltaCookie.String()) - cr := http.Request{Header: ch} - - session := emptyTestSession(t) - baseURL, err := url.Parse(setting.AppURL) - assert.NoError(t, err) - session.jar.SetCookies(baseURL, cr.Cookies()) - - return session -} - -// GetLTACookieValue returns the value of the LTA cookie. -func GetLTACookieValue(t *testing.T, sess *TestSession) string { - t.Helper() - - rememberCookie := sess.GetCookie(setting.CookieRememberName) - assert.NotNil(t, rememberCookie) - - cookieValue, err := url.QueryUnescape(rememberCookie.Value) - assert.NoError(t, err) - - return cookieValue -} - -// TestSessionCookie checks if the session cookie provides authentication. -func TestSessionCookie(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - sess := loginUser(t, "user1") - assert.NotNil(t, sess.GetCookie(setting.SessionConfig.CookieName)) - - req := NewRequest(t, "GET", "/user/settings") - sess.MakeRequest(t, req, http.StatusOK) -} - -// TestLTACookie checks if the LTA cookie that's returned is valid, exists in the database -// and provides authentication of no session cookie is present. -func TestLTACookie(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - sess := emptyTestSession(t) - - req := NewRequestWithValues(t, "POST", "/user/login", map[string]string{ - "_csrf": GetCSRF(t, sess, "/user/login"), - "user_name": user.Name, - "password": userPassword, - "remember": "true", - }) - sess.MakeRequest(t, req, http.StatusSeeOther) - - // Checks if the database entry exist for the user. - ltaCookieValue := GetLTACookieValue(t, sess) - lookupKey, validator, found := strings.Cut(ltaCookieValue, ":") - assert.True(t, found) - rawValidator, err := hex.DecodeString(validator) - assert.NoError(t, err) - unittest.AssertExistsAndLoadBean(t, &auth.AuthorizationToken{LookupKey: lookupKey, HashedValidator: auth.HashValidator(rawValidator), UID: user.ID}) - - // Check if the LTA cookie it provides authentication. - // If LTA cookie provides authentication /user/login shouldn't return status 200. - session := GetSessionForLTACookie(t, sess.GetCookie(setting.CookieRememberName)) - req = NewRequest(t, "GET", "/user/login") - session.MakeRequest(t, req, http.StatusSeeOther) -} - -// TestLTAPasswordChange checks that LTA doesn't provide authentication when a -// password change has happened and that the new LTA does provide authentication. -func TestLTAPasswordChange(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - - sess := loginUserWithPasswordRemember(t, user.Name, userPassword, true) - oldRememberCookie := sess.GetCookie(setting.CookieRememberName) - assert.NotNil(t, oldRememberCookie) - - // Make a simple password change. - req := NewRequestWithValues(t, "POST", "/user/settings/account", map[string]string{ - "_csrf": GetCSRF(t, sess, "/user/settings/account"), - "old_password": userPassword, - "password": "password2", - "retype": "password2", - }) - sess.MakeRequest(t, req, http.StatusSeeOther) - rememberCookie := sess.GetCookie(setting.CookieRememberName) - assert.NotNil(t, rememberCookie) - - // Check if the password really changed. - assert.NotEqualValues(t, unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}).Passwd, user.Passwd) - - // /user/settings/account should provide with a new LTA cookie, so check for that. - // If LTA cookie provides authentication /user/login shouldn't return status 200. - session := GetSessionForLTACookie(t, rememberCookie) - req = NewRequest(t, "GET", "/user/login") - session.MakeRequest(t, req, http.StatusSeeOther) - - // Check if the old LTA token is invalidated. - session = GetSessionForLTACookie(t, oldRememberCookie) - req = NewRequest(t, "GET", "/user/login") - session.MakeRequest(t, req, http.StatusOK) -} - -// TestLTAExpiry tests that the LTA expiry works. -func TestLTAExpiry(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) - - sess := loginUserWithPasswordRemember(t, user.Name, userPassword, true) - - ltaCookieValie := GetLTACookieValue(t, sess) - lookupKey, _, found := strings.Cut(ltaCookieValie, ":") - assert.True(t, found) - - // Ensure it's not expired. - lta := unittest.AssertExistsAndLoadBean(t, &auth.AuthorizationToken{UID: user.ID, LookupKey: lookupKey}) - assert.False(t, lta.IsExpired()) - - // Manually stub LTA's expiry. - _, err := db.GetEngine(db.DefaultContext).ID(lta.ID).Table("forgejo_auth_token").Cols("expiry").Update(&auth.AuthorizationToken{Expiry: timeutil.TimeStampNow()}) - assert.NoError(t, err) - - // Ensure it's expired. - lta = unittest.AssertExistsAndLoadBean(t, &auth.AuthorizationToken{UID: user.ID, LookupKey: lookupKey}) - assert.True(t, lta.IsExpired()) - - // Should return 200 OK, because LTA doesn't provide authorization anymore. - session := GetSessionForLTACookie(t, sess.GetCookie(setting.CookieRememberName)) - req := NewRequest(t, "GET", "/user/login") - session.MakeRequest(t, req, http.StatusOK) - - // Ensure it's deleted. - unittest.AssertNotExistsBean(t, &auth.AuthorizationToken{UID: user.ID, LookupKey: lookupKey}) -} diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index cb6269cea9..6b00f3575b 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -17,7 +17,6 @@ import ( "net/url" "os" "path/filepath" - "strconv" "strings" "sync/atomic" "testing" @@ -300,12 +299,6 @@ func loginUser(t testing.TB, userName string) *TestSession { func loginUserWithPassword(t testing.TB, userName, password string) *TestSession { t.Helper() - - return loginUserWithPasswordRemember(t, userName, password, false) -} - -func loginUserWithPasswordRemember(t testing.TB, userName, password string, rememberMe bool) *TestSession { - t.Helper() req := NewRequest(t, "GET", "/user/login") resp := MakeRequest(t, req, http.StatusOK) @@ -314,7 +307,6 @@ func loginUserWithPasswordRemember(t testing.TB, userName, password string, reme "_csrf": doc.GetCSRF(), "user_name": userName, "password": password, - "remember": strconv.FormatBool(rememberMe), }) resp = MakeRequest(t, req, http.StatusSeeOther)