mirror of
https://github.com/go-gitea/gitea.git
synced 2026-06-28 14:35:48 +02:00
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>
218 lines
6.0 KiB
Go
218 lines
6.0 KiB
Go
// Copyright 2019 Gitea. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package admin
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"gitea.dev/models/db"
|
|
repo_model "gitea.dev/models/repo"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/json"
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/migration"
|
|
"gitea.dev/modules/secret"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/structs"
|
|
"gitea.dev/modules/timeutil"
|
|
"gitea.dev/modules/util"
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// Task represents a task
|
|
type Task struct {
|
|
ID int64
|
|
DoerID int64 `xorm:"index"` // operator
|
|
Doer *user_model.User `xorm:"-"`
|
|
OwnerID int64 `xorm:"index"` // repo owner id, when creating, the repoID maybe zero
|
|
Owner *user_model.User `xorm:"-"`
|
|
RepoID int64 `xorm:"index"`
|
|
Repo *repo_model.Repository `xorm:"-"`
|
|
Type structs.TaskType
|
|
Status structs.TaskStatus `xorm:"index"`
|
|
StartTime timeutil.TimeStamp
|
|
EndTime timeutil.TimeStamp
|
|
PayloadContent string `xorm:"TEXT"`
|
|
Message string `xorm:"TEXT"` // if task failed, saved the error reason, it could be a JSON string of TranslatableMessage or a plain message
|
|
Created timeutil.TimeStamp `xorm:"created"`
|
|
}
|
|
|
|
func init() {
|
|
db.RegisterModel(new(Task))
|
|
}
|
|
|
|
// TranslatableMessage represents JSON struct that can be translated with a Locale
|
|
type TranslatableMessage struct {
|
|
Format string
|
|
Args []any `json:",omitempty"`
|
|
}
|
|
|
|
// LoadRepo loads repository of the task
|
|
func (task *Task) LoadRepo(ctx context.Context) error {
|
|
if task.Repo != nil {
|
|
return nil
|
|
}
|
|
var repo repo_model.Repository
|
|
has, err := db.GetEngine(ctx).ID(task.RepoID).Get(&repo)
|
|
if err != nil {
|
|
return err
|
|
} else if !has {
|
|
return repo_model.ErrRepoNotExist{
|
|
ID: task.RepoID,
|
|
}
|
|
}
|
|
task.Repo = &repo
|
|
return nil
|
|
}
|
|
|
|
// LoadDoer loads do user
|
|
func (task *Task) LoadDoer(ctx context.Context) error {
|
|
if task.Doer != nil {
|
|
return nil
|
|
}
|
|
|
|
var doer user_model.User
|
|
has, err := db.GetEngine(ctx).ID(task.DoerID).Get(&doer)
|
|
if err != nil {
|
|
return err
|
|
} else if !has {
|
|
return user_model.ErrUserNotExist{
|
|
UID: task.DoerID,
|
|
}
|
|
}
|
|
task.Doer = &doer
|
|
|
|
return nil
|
|
}
|
|
|
|
// LoadOwner loads owner user
|
|
func (task *Task) LoadOwner(ctx context.Context) error {
|
|
if task.Owner != nil {
|
|
return nil
|
|
}
|
|
|
|
var owner user_model.User
|
|
has, err := db.GetEngine(ctx).ID(task.OwnerID).Get(&owner)
|
|
if err != nil {
|
|
return err
|
|
} else if !has {
|
|
return user_model.ErrUserNotExist{
|
|
UID: task.OwnerID,
|
|
}
|
|
}
|
|
task.Owner = &owner
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateCols updates some columns
|
|
func (task *Task) UpdateCols(ctx context.Context, cols ...string) error {
|
|
_, err := db.GetEngine(ctx).ID(task.ID).Cols(cols...).Update(task)
|
|
return err
|
|
}
|
|
|
|
// MigrateConfig returns task config when migrate repository
|
|
func (task *Task) MigrateConfig() (*migration.MigrateOptions, error) {
|
|
if task.Type == structs.TaskTypeMigrateRepo {
|
|
var opts migration.MigrateOptions
|
|
err := json.Unmarshal([]byte(task.PayloadContent), &opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// decrypt credentials
|
|
if opts.CloneAddrEncrypted != "" {
|
|
if opts.CloneAddr, err = secret.DecryptSecret(setting.SecretKey, opts.CloneAddrEncrypted); err != nil {
|
|
log.Error("Unable to decrypt CloneAddr, maybe SECRET_KEY is wrong: %v", err)
|
|
}
|
|
}
|
|
if opts.AuthPasswordEncrypted != "" {
|
|
if opts.AuthPassword, err = secret.DecryptSecret(setting.SecretKey, opts.AuthPasswordEncrypted); err != nil {
|
|
log.Error("Unable to decrypt AuthPassword, maybe SECRET_KEY is wrong: %v", err)
|
|
}
|
|
}
|
|
if opts.AuthTokenEncrypted != "" {
|
|
if opts.AuthToken, err = secret.DecryptSecret(setting.SecretKey, opts.AuthTokenEncrypted); err != nil {
|
|
log.Error("Unable to decrypt AuthToken, maybe SECRET_KEY is wrong: %v", err)
|
|
}
|
|
}
|
|
if opts.AWSSecretAccessKeyEncrypted != "" {
|
|
if opts.AWSSecretAccessKey, err = secret.DecryptSecret(setting.SecretKey, opts.AWSSecretAccessKeyEncrypted); err != nil {
|
|
log.Error("Unable to decrypt AWSSecretAccessKey, maybe SECRET_KEY is wrong: %v", err)
|
|
}
|
|
}
|
|
|
|
return &opts, nil
|
|
}
|
|
return nil, fmt.Errorf("Task type is %s, not Migrate Repo", task.Type.Name())
|
|
}
|
|
|
|
// ErrTaskDoesNotExist represents a "TaskDoesNotExist" kind of error.
|
|
type ErrTaskDoesNotExist struct {
|
|
ID int64
|
|
RepoID int64
|
|
Type structs.TaskType
|
|
}
|
|
|
|
// IsErrTaskDoesNotExist checks if an error is a ErrTaskDoesNotExist.
|
|
func IsErrTaskDoesNotExist(err error) bool {
|
|
_, ok := err.(ErrTaskDoesNotExist)
|
|
return ok
|
|
}
|
|
|
|
func (err ErrTaskDoesNotExist) Error() string {
|
|
return fmt.Sprintf("task does not exist [id: %d, repo_id: %d, type: %d]",
|
|
err.ID, err.RepoID, err.Type)
|
|
}
|
|
|
|
func (err ErrTaskDoesNotExist) Unwrap() error {
|
|
return util.ErrNotExist
|
|
}
|
|
|
|
// GetMigratingTask returns the migrating task by repo's id
|
|
func GetMigratingTask(ctx context.Context, repoID int64) (*Task, error) {
|
|
task, has, err := db.Get[Task](ctx, builder.Eq{"repo_id": repoID, "`type`": structs.TaskTypeMigrateRepo})
|
|
if err != nil {
|
|
return nil, err
|
|
} else if !has {
|
|
return nil, ErrTaskDoesNotExist{0, repoID, structs.TaskTypeMigrateRepo}
|
|
}
|
|
return task, nil
|
|
}
|
|
|
|
// CreateTask creates a task on database
|
|
func CreateTask(ctx context.Context, task *Task) error {
|
|
return db.Insert(ctx, task)
|
|
}
|
|
|
|
// FinishMigrateTask updates database when migrate task finished
|
|
func FinishMigrateTask(ctx context.Context, task *Task) error {
|
|
task.Status = structs.TaskStatusFinished
|
|
task.EndTime = timeutil.TimeStampNow()
|
|
|
|
// delete credentials when we're done, they're a liability.
|
|
conf, err := task.MigrateConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
conf.AuthPassword = ""
|
|
conf.AuthToken = ""
|
|
conf.CloneAddr = util.SanitizeCredentialURLs(conf.CloneAddr)
|
|
conf.AuthPasswordEncrypted = ""
|
|
conf.AuthTokenEncrypted = ""
|
|
conf.CloneAddrEncrypted = ""
|
|
conf.AWSSecretAccessKey = ""
|
|
conf.AWSSecretAccessKeyEncrypted = ""
|
|
confBytes, err := json.Marshal(conf)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
task.PayloadContent = string(confBytes)
|
|
|
|
_, err = db.GetEngine(ctx).ID(task.ID).Cols("status", "end_time", "payload_content").Update(task)
|
|
return err
|
|
}
|