mirror of
https://github.com/go-gitea/gitea.git
synced 2026-08-10 15:42:28 +00:00
cbe1b703dc
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>
144 lines
3.8 KiB
Go
144 lines
3.8 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package auth
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
|
|
"gitea.dev/models/auth"
|
|
"gitea.dev/models/db"
|
|
user_model "gitea.dev/models/user"
|
|
"gitea.dev/modules/log"
|
|
"gitea.dev/modules/optional"
|
|
"gitea.dev/services/auth/source/oauth2"
|
|
"gitea.dev/services/auth/source/smtp"
|
|
|
|
_ "gitea.dev/services/auth/source/db" // register the sources (and below)
|
|
_ "gitea.dev/services/auth/source/ldap" // register the ldap source
|
|
_ "gitea.dev/services/auth/source/pam" // register the pam source
|
|
_ "gitea.dev/services/auth/source/sspi" // register the sspi source
|
|
|
|
"xorm.io/builder"
|
|
)
|
|
|
|
// UserSignIn validates user name and password.
|
|
func UserSignIn(ctx context.Context, username, password string) (*user_model.User, *auth.Source, error) {
|
|
var user *user_model.User
|
|
isEmail := false
|
|
if strings.Contains(username, "@") {
|
|
isEmail = true
|
|
// check same email
|
|
emailAddress, has, err := db.Get[user_model.EmailAddress](ctx, builder.Eq{"lower_email": strings.ToLower(strings.TrimSpace(username))})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if has {
|
|
if !emailAddress.IsActivated {
|
|
return nil, nil, user_model.ErrEmailAddressNotExist{
|
|
Email: username,
|
|
}
|
|
}
|
|
user = &user_model.User{ID: emailAddress.UID}
|
|
}
|
|
} else {
|
|
trimmedUsername := strings.TrimSpace(username)
|
|
if len(trimmedUsername) == 0 {
|
|
return nil, nil, user_model.ErrUserNotExist{Name: username}
|
|
}
|
|
|
|
user = &user_model.User{LowerName: strings.ToLower(trimmedUsername)}
|
|
}
|
|
|
|
if user != nil {
|
|
var hasUser bool
|
|
var err error
|
|
if user.ID > 0 {
|
|
user, err = user_model.GetUserByID(ctx, user.ID)
|
|
if err != nil && !user_model.IsErrUserNotExist(err) {
|
|
return nil, nil, err
|
|
}
|
|
if user != nil && user.Type != user_model.UserTypeIndividual {
|
|
return nil, nil, user_model.ErrUserNotExist{Name: username}
|
|
}
|
|
hasUser = user != nil
|
|
} else if user.LowerName != "" {
|
|
user, err = user_model.GetIndividualUserByName(ctx, user.LowerName)
|
|
if err != nil && !user_model.IsErrUserNotExist(err) {
|
|
return nil, nil, err
|
|
}
|
|
hasUser = user != nil
|
|
}
|
|
|
|
if hasUser {
|
|
source, err := auth.GetSourceByID(ctx, user.LoginSource)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
if !source.IsActive {
|
|
return nil, nil, oauth2.ErrAuthSourceNotActivated
|
|
}
|
|
|
|
authenticator, ok := source.Cfg.(PasswordAuthenticator)
|
|
if !ok {
|
|
return nil, nil, smtp.ErrUnsupportedLoginType
|
|
}
|
|
|
|
user, err := authenticator.Authenticate(ctx, user, user.LoginName, password)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
|
|
// user could be hint to resend confirm email.
|
|
if user.ProhibitLogin {
|
|
return nil, nil, user_model.ErrUserProhibitLogin{UID: user.ID, Name: user.Name}
|
|
}
|
|
|
|
return user, source, nil
|
|
}
|
|
}
|
|
|
|
sources, err := db.Find[auth.Source](ctx, auth.FindSourcesOptions{
|
|
IsActive: optional.Some(true),
|
|
})
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
for _, source := range sources {
|
|
if !source.IsActive {
|
|
// don't try to authenticate non-active sources
|
|
continue
|
|
}
|
|
|
|
authenticator, ok := source.Cfg.(PasswordAuthenticator)
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
authUser, err := authenticator.Authenticate(ctx, nil, username, password)
|
|
|
|
if err == nil {
|
|
if !authUser.ProhibitLogin {
|
|
return authUser, source, nil
|
|
}
|
|
err = user_model.ErrUserProhibitLogin{UID: authUser.ID, Name: authUser.Name}
|
|
}
|
|
|
|
if user_model.IsErrUserNotExist(err) {
|
|
log.Debug("Failed to login '%s' via '%s': %v", username, source.Name, err)
|
|
} else {
|
|
log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
|
|
}
|
|
}
|
|
|
|
if isEmail {
|
|
return nil, nil, user_model.ErrEmailAddressNotExist{Email: username}
|
|
}
|
|
|
|
return nil, nil, user_model.ErrUserNotExist{Name: username}
|
|
}
|