| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 | // Copyright 2020 The Gogs Authors. All rights reserved.// Use of this source code is governed by a MIT-style// license that can be found in the LICENSE file.package dbimport (	"context"	"fmt"	"strings"	"time"	"github.com/go-macaron/binding"	"github.com/pkg/errors"	"gorm.io/gorm"	"gogs.io/gogs/internal/auth"	"gogs.io/gogs/internal/cryptoutil"	"gogs.io/gogs/internal/errutil")// UsersStore is the persistent interface for users.//// NOTE: All methods are sorted in alphabetical order.type UsersStore interface {	// Authenticate validates username and password via given login source ID. It	// returns ErrUserNotExist when the user was not found.	//	// When the "loginSourceID" is negative, it aborts the process and returns	// ErrUserNotExist if the user was not found in the database.	//	// When the "loginSourceID" is non-negative, it returns ErrLoginSourceMismatch	// if the user has different login source ID than the "loginSourceID".	//	// When the "loginSourceID" is positive, it tries to authenticate via given	// login source and creates a new user when not yet exists in the database.	Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)	// Create creates a new user and persists to database. It returns	// ErrUserAlreadyExist when a user with same name already exists, or	// ErrEmailAlreadyUsed if the email has been used by another user.	Create(ctx context.Context, username, email string, opts CreateUserOpts) (*User, error)	// GetByEmail returns the user (not organization) with given email. It ignores	// records with unverified emails and returns ErrUserNotExist when not found.	GetByEmail(ctx context.Context, email string) (*User, error)	// GetByID returns the user with given ID. It returns ErrUserNotExist when not	// found.	GetByID(ctx context.Context, id int64) (*User, error)	// GetByUsername returns the user with given username. It returns	// ErrUserNotExist when not found.	GetByUsername(ctx context.Context, username string) (*User, error)}var Users UsersStore// BeforeCreate implements the GORM create hook.func (u *User) BeforeCreate(tx *gorm.DB) error {	if u.CreatedUnix == 0 {		u.CreatedUnix = tx.NowFunc().Unix()		u.UpdatedUnix = u.CreatedUnix	}	return nil}// AfterFind implements the GORM query hook.func (u *User) AfterFind(_ *gorm.DB) error {	u.Created = time.Unix(u.CreatedUnix, 0).Local()	u.Updated = time.Unix(u.UpdatedUnix, 0).Local()	return nil}var _ UsersStore = (*users)(nil)type users struct {	*gorm.DB}type ErrLoginSourceMismatch struct {	args errutil.Args}func (err ErrLoginSourceMismatch) Error() string {	return fmt.Sprintf("login source mismatch: %v", err.args)}func (db *users) Authenticate(ctx context.Context, login, password string, loginSourceID int64) (*User, error) {	login = strings.ToLower(login)	query := db.WithContext(ctx)	if strings.Contains(login, "@") {		query = query.Where("email = ?", login)	} else {		query = query.Where("lower_name = ?", login)	}	user := new(User)	err := query.First(user).Error	if err != nil && err != gorm.ErrRecordNotFound {		return nil, errors.Wrap(err, "get user")	}	var authSourceID int64 // The login source ID will be used to authenticate the user	createNewUser := false // Whether to create a new user after successful authentication	// User found in the database	if err == nil {		// Note: This check is unnecessary but to reduce user confusion at login page		// and make it more consistent from user's perspective.		if loginSourceID >= 0 && user.LoginSource != loginSourceID {			return nil, ErrLoginSourceMismatch{args: errutil.Args{"expect": loginSourceID, "actual": user.LoginSource}}		}		// Validate password hash fetched from database for local accounts.		if user.IsLocal() {			if user.ValidatePassword(password) {				return user, nil			}			return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login, "userID": user.ID}}		}		authSourceID = user.LoginSource	} else {		// Non-local login source is always greater than 0.		if loginSourceID <= 0 {			return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login}}		}		authSourceID = loginSourceID		createNewUser = true	}	source, err := LoginSources.GetByID(ctx, authSourceID)	if err != nil {		return nil, errors.Wrap(err, "get login source")	}	if !source.IsActived {		return nil, errors.Errorf("login source %d is not activated", source.ID)	}	extAccount, err := source.Provider.Authenticate(login, password)	if err != nil {		return nil, err	}	if !createNewUser {		return user, nil	}	// Validate username make sure it satisfies requirement.	if binding.AlphaDashDotPattern.MatchString(extAccount.Name) {		return nil, fmt.Errorf("invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", extAccount.Name)	}	return db.Create(ctx, extAccount.Name, extAccount.Email,		CreateUserOpts{			FullName:    extAccount.FullName,			LoginSource: authSourceID,			LoginName:   extAccount.Login,			Location:    extAccount.Location,			Website:     extAccount.Website,			Activated:   true,			Admin:       extAccount.Admin,		},	)}type CreateUserOpts struct {	FullName    string	Password    string	LoginSource int64	LoginName   string	Location    string	Website     string	Activated   bool	Admin       bool}type ErrUserAlreadyExist struct {	args errutil.Args}func IsErrUserAlreadyExist(err error) bool {	_, ok := err.(ErrUserAlreadyExist)	return ok}func (err ErrUserAlreadyExist) Error() string {	return fmt.Sprintf("user already exists: %v", err.args)}type ErrEmailAlreadyUsed struct {	args errutil.Args}func IsErrEmailAlreadyUsed(err error) bool {	_, ok := err.(ErrEmailAlreadyUsed)	return ok}func (err ErrEmailAlreadyUsed) Email() string {	email, ok := err.args["email"].(string)	if ok {		return email	}	return "<email not found>"}func (err ErrEmailAlreadyUsed) Error() string {	return fmt.Sprintf("email has been used: %v", err.args)}func (db *users) Create(ctx context.Context, username, email string, opts CreateUserOpts) (*User, error) {	err := isUsernameAllowed(username)	if err != nil {		return nil, err	}	_, err = db.GetByUsername(ctx, username)	if err == nil {		return nil, ErrUserAlreadyExist{args: errutil.Args{"name": username}}	} else if !IsErrUserNotExist(err) {		return nil, err	}	_, err = db.GetByEmail(ctx, email)	if err == nil {		return nil, ErrEmailAlreadyUsed{args: errutil.Args{"email": email}}	} else if !IsErrUserNotExist(err) {		return nil, err	}	user := &User{		LowerName:       strings.ToLower(username),		Name:            username,		FullName:        opts.FullName,		Email:           email,		Passwd:          opts.Password,		LoginSource:     opts.LoginSource,		LoginName:       opts.LoginName,		Location:        opts.Location,		Website:         opts.Website,		MaxRepoCreation: -1,		IsActive:        opts.Activated,		IsAdmin:         opts.Admin,		Avatar:          cryptoutil.MD5(email),		AvatarEmail:     email,	}	user.Rands, err = GetUserSalt()	if err != nil {		return nil, err	}	user.Salt, err = GetUserSalt()	if err != nil {		return nil, err	}	user.EncodePassword()	return user, db.WithContext(ctx).Create(user).Error}var _ errutil.NotFound = (*ErrUserNotExist)(nil)type ErrUserNotExist struct {	args errutil.Args}func IsErrUserNotExist(err error) bool {	_, ok := err.(ErrUserNotExist)	return ok}func (err ErrUserNotExist) Error() string {	return fmt.Sprintf("user does not exist: %v", err.args)}func (ErrUserNotExist) NotFound() bool {	return true}func (db *users) GetByEmail(ctx context.Context, email string) (*User, error) {	email = strings.ToLower(email)	if email == "" {		return nil, ErrUserNotExist{args: errutil.Args{"email": email}}	}	// First try to find the user by primary email	user := new(User)	err := db.WithContext(ctx).		Where("email = ? AND type = ? AND is_active = ?", email, UserIndividual, true).		First(user).		Error	if err == nil {		return user, nil	} else if err != gorm.ErrRecordNotFound {		return nil, err	}	// Otherwise, check activated email addresses	emailAddress := new(EmailAddress)	err = db.WithContext(ctx).		Where("email = ? AND is_activated = ?", email, true).		First(emailAddress).		Error	if err != nil {		if err == gorm.ErrRecordNotFound {			return nil, ErrUserNotExist{args: errutil.Args{"email": email}}		}		return nil, err	}	return db.GetByID(ctx, emailAddress.UID)}func (db *users) GetByID(ctx context.Context, id int64) (*User, error) {	user := new(User)	err := db.WithContext(ctx).Where("id = ?", id).First(user).Error	if err != nil {		if err == gorm.ErrRecordNotFound {			return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}		}		return nil, err	}	return user, nil}func (db *users) GetByUsername(ctx context.Context, username string) (*User, error) {	user := new(User)	err := db.WithContext(ctx).Where("lower_name = ?", strings.ToLower(username)).First(user).Error	if err != nil {		if err == gorm.ErrRecordNotFound {			return nil, ErrUserNotExist{args: errutil.Args{"name": username}}		}		return nil, err	}	return user, nil}
 |