refactor: Use db.Get[] instead of db.GetEngine(ctx).Get(bean) to avoid zero value fetching wrong database record (#37977)

This PR replaces a set of struct-based `Get` lookups with explicit
`db.Get` / `db.Exist` conditions in places where zero-value fields can
lead to ambiguous matches or incorrect records being returned.

The main goal is to make read paths deterministic and avoid accidentally
matching the wrong row when only part of a struct is populated.

### What changed

- replace many `db.GetEngine(ctx).Get(bean)` calls with explicit
`builder.Eq` conditions across models such as actions, admin tasks,
issues, pull requests, repositories, users, packages, redirects,
watches, stars, and follows
- use quoted column names where needed for reserved fields like `index`,
`type`, and `name`
- add dedicated user lookup helpers for:
  - primary email
  - OAuth login source / login name
- update sign-in and OAuth-related flows to use explicit individual-user
lookups instead of partially populated `User` structs
- tighten package property and Terraform lock lookups to avoid ambiguous
reads and updates
- keep existing fallback behavior where needed, while removing reliance
on zero-value struct matching

### User-facing impact

These changes primarily affect authentication and account lookup paths:

- email/username sign-in now re-fetches users through explicit keys
- OAuth2 auto-linking now resolves users by name or primary email
explicitly
- OAuth2 login/sync now looks up users by login source, login type, and
login name explicitly
- non-individual accounts are no longer implicitly matched through
partial user lookups in these flows

This should reduce the risk of incorrect account matches and make query
behavior more predictable across the codebase.

---------

Co-authored-by: bircni <bircni@icloud.com>
This commit is contained in:
Lunny Xiao
2026-06-27 10:24:02 -07:00
committed by GitHub
co-authored by bircni
parent d5e6f273f0
commit cbe1b703dc
35 changed files with 161 additions and 153 deletions
+3 -2
View File
@@ -17,6 +17,8 @@ import (
"gitea.dev/modules/storage"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"xorm.io/builder"
)
// Attachment represent a attachment of issue/comment/release.
@@ -156,8 +158,7 @@ func GetAttachmentsByCommentID(ctx context.Context, commentID int64) ([]*Attachm
// GetAttachmentByReleaseIDFileName returns attachment by given releaseId and fileName.
func GetAttachmentByReleaseIDFileName(ctx context.Context, releaseID int64, fileName string) (*Attachment, error) {
attach := &Attachment{ReleaseID: releaseID, Name: fileName}
has, err := db.GetEngine(ctx).Get(attach)
attach, has, err := db.Get[Attachment](ctx, builder.Eq{"release_id": releaseID, "`name`": fileName})
if err != nil {
return nil, err
} else if !has {
+6 -19
View File
@@ -102,20 +102,13 @@ func GetCollaborators(ctx context.Context, opts *FindCollaborationOptions) ([]*C
// GetCollaboration get collaboration for a repository id with a user id
func GetCollaboration(ctx context.Context, repoID, uid int64) (*Collaboration, error) {
collaboration := &Collaboration{
RepoID: repoID,
UserID: uid,
}
has, err := db.GetEngine(ctx).Get(collaboration)
if !has {
collaboration = nil
}
collaboration, _, err := db.Get[Collaboration](ctx, builder.Eq{"repo_id": repoID, "user_id": uid})
return collaboration, err
}
// IsCollaborator check if a user is a collaborator of a repository
func IsCollaborator(ctx context.Context, repoID, userID int64) (bool, error) {
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repoID, UserID: userID})
return db.Exist[Collaboration](ctx, builder.Eq{"repo_id": repoID, "user_id": userID})
}
// ChangeCollaborationAccessMode sets new access mode for the collaboration.
@@ -126,13 +119,7 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in
}
return db.WithTx(ctx, func(ctx context.Context) error {
e := db.GetEngine(ctx)
collaboration := &Collaboration{
RepoID: repo.ID,
UserID: uid,
}
has, err := e.Get(collaboration)
collaboration, has, err := db.Get[Collaboration](ctx, builder.Eq{"repo_id": repo.ID, "user_id": uid})
if err != nil {
return fmt.Errorf("get collaboration: %w", err)
} else if !has {
@@ -144,12 +131,12 @@ func ChangeCollaborationAccessMode(ctx context.Context, repo *Repository, uid in
}
collaboration.Mode = mode
if _, err = e.
if _, err = db.GetEngine(ctx).
ID(collaboration.ID).
Cols("mode").
Update(collaboration); err != nil {
return fmt.Errorf("update collaboration: %w", err)
} else if _, err = e.Exec("UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
} else if _, err = db.Exec(ctx, "UPDATE access SET mode = ? WHERE user_id = ? AND repo_id = ?", mode, uid, repo.ID); err != nil {
return fmt.Errorf("update access table: %w", err)
}
@@ -174,5 +161,5 @@ func IsOwnerMemberCollaborator(ctx context.Context, repo *Repository, userID int
return true, nil
}
return db.GetEngine(ctx).Get(&Collaboration{RepoID: repo.ID, UserID: userID})
return db.Exist[Collaboration](ctx, builder.Eq{"repo_id": repo.ID, "user_id": userID})
}
+3 -2
View File
@@ -12,6 +12,8 @@ import (
"gitea.dev/modules/log"
"gitea.dev/modules/timeutil"
"gitea.dev/modules/util"
"xorm.io/builder"
)
// ErrMirrorNotExist mirror does not exist error
@@ -76,8 +78,7 @@ func (m *Mirror) ScheduleNextUpdate() {
// GetMirrorByRepoID returns mirror information of a repository.
func GetMirrorByRepoID(ctx context.Context, repoID int64) (*Mirror, error) {
m := &Mirror{RepoID: repoID}
has, err := db.GetEngine(ctx).Get(m)
m, has, err := db.Get[Mirror](ctx, builder.Eq{"repo_id": repoID})
if err != nil {
return nil, err
} else if !has {
+4 -2
View File
@@ -10,6 +10,8 @@ import (
"gitea.dev/models/db"
"gitea.dev/modules/util"
"xorm.io/builder"
)
// ErrRedirectNotExist represents a "RedirectNotExist" kind of error.
@@ -52,8 +54,8 @@ func init() {
// LookupRedirect look up if a repository has a redirect name
func LookupRedirect(ctx context.Context, ownerID int64, repoName string) (int64, error) {
repoName = strings.ToLower(repoName)
redirect := &Redirect{OwnerID: ownerID, LowerName: repoName}
if has, err := db.GetEngine(ctx).Get(redirect); err != nil {
redirect, has, err := db.Get[Redirect](ctx, builder.Eq{"owner_id": ownerID, "lower_name": repoName})
if err != nil {
return 0, err
} else if !has {
return 0, ErrRedirectNotExist{OwnerID: ownerID, RepoName: repoName}
+1 -2
View File
@@ -216,8 +216,7 @@ func AddReleaseAttachments(ctx context.Context, releaseID int64, attachmentUUIDs
// GetRelease returns release by given ID.
func GetRelease(ctx context.Context, repoID int64, tagName string) (*Release, error) {
rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
has, err := db.GetEngine(ctx).Get(rel)
rel, has, err := db.Get[Release](ctx, builder.Eq{"repo_id": repoID, "lower_tag_name": strings.ToLower(tagName)})
if err != nil {
return nil, err
} else if !has {
+3 -3
View File
@@ -849,9 +849,9 @@ func GetRepositoriesMapByIDs(ctx context.Context, ids []int64) (map[int64]*Repos
}
func IsRepositoryModelExist(ctx context.Context, u *user_model.User, repoName string) (bool, error) {
return db.GetEngine(ctx).Get(&Repository{
OwnerID: u.ID,
LowerName: strings.ToLower(repoName),
return db.Exist[Repository](ctx, builder.Eq{
"owner_id": u.ID,
"lower_name": strings.ToLower(repoName),
})
}
+3 -1
View File
@@ -9,6 +9,8 @@ import (
"gitea.dev/models/db"
user_model "gitea.dev/models/user"
"gitea.dev/modules/timeutil"
"xorm.io/builder"
)
// Star represents a starred repo by a user.
@@ -68,7 +70,7 @@ func StarRepo(ctx context.Context, doer *user_model.User, repo *Repository, star
// IsStaring checks if user has starred given repository.
func IsStaring(ctx context.Context, userID, repoID int64) bool {
has, _ := db.GetEngine(ctx).Get(&Star{UID: userID, RepoID: repoID})
has, _ := db.Exist[Star](ctx, builder.Eq{"uid": userID, "repo_id": repoID})
return has
}
+8 -4
View File
@@ -10,6 +10,8 @@ import (
user_model "gitea.dev/models/user"
"gitea.dev/modules/setting"
"gitea.dev/modules/timeutil"
"xorm.io/builder"
)
// WatchMode specifies what kind of watch the user has on a repository
@@ -41,12 +43,14 @@ func init() {
}
// GetWatch gets what kind of subscription a user has on a given repository; returns dummy record if none found
func GetWatch(ctx context.Context, userID, repoID int64) (Watch, error) {
watch := Watch{UserID: userID, RepoID: repoID}
has, err := db.GetEngine(ctx).Get(&watch)
func GetWatch(ctx context.Context, userID, repoID int64) (*Watch, error) {
watch, has, err := db.Get[Watch](ctx, builder.Eq{"user_id": userID, "repo_id": repoID})
if err != nil {
return watch, err
}
if watch == nil {
watch = &Watch{UserID: userID, RepoID: repoID}
}
if !has {
watch.Mode = WatchModeNone
}
@@ -64,7 +68,7 @@ func IsWatching(ctx context.Context, userID, repoID int64) bool {
return err == nil && IsWatchMode(watch.Mode)
}
func watchRepoMode(ctx context.Context, watch Watch, mode WatchMode) (err error) {
func watchRepoMode(ctx context.Context, watch *Watch, mode WatchMode) (err error) {
if watch.Mode == mode {
return nil
}