refactor(db): migrate methods off `user.go` (#7230)

pull/7231/head
Joe Chen 2022-11-05 18:56:48 +08:00 committed by GitHub
parent fd798b4197
commit b5d47b9692
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 273 additions and 211 deletions

View File

@ -5,6 +5,7 @@
package cmd
import (
"context"
"fmt"
"reflect"
"runtime"
@ -151,17 +152,21 @@ func runCreateUser(c *cli.Context) error {
return errors.Wrap(err, "set engine")
}
if err := db.CreateUser(&db.User{
Name: c.String("name"),
Email: c.String("email"),
Password: c.String("password"),
IsActive: true,
IsAdmin: c.Bool("admin"),
}); err != nil {
return fmt.Errorf("CreateUser: %v", err)
user, err := db.Users.Create(
context.Background(),
c.String("name"),
c.String("email"),
db.CreateUserOptions{
Password: c.String("password"),
Activated: true,
Admin: c.Bool("admin"),
},
)
if err != nil {
return errors.Wrap(err, "create user")
}
fmt.Printf("New user '%s' has been successfully created!\n", c.String("name"))
fmt.Printf("New user %q has been successfully created!\n", user.Name)
return nil
}

View File

@ -175,7 +175,7 @@ func authenticatedUser(ctx *macaron.Context, sess session.Store) (_ *db.User, is
if conf.Auth.EnableReverseProxyAuthentication {
webAuthUser := ctx.Req.Header.Get(conf.Auth.ReverseProxyAuthenticationHeader)
if len(webAuthUser) > 0 {
u, err := db.GetUserByName(webAuthUser)
user, err := db.GetUserByName(webAuthUser)
if err != nil {
if !db.IsErrUserNotExist(err) {
log.Error("Failed to get user by name: %v", err)
@ -184,22 +184,21 @@ func authenticatedUser(ctx *macaron.Context, sess session.Store) (_ *db.User, is
// Check if enabled auto-registration.
if conf.Auth.EnableReverseProxyAutoRegistration {
u := &db.User{
Name: webAuthUser,
Email: gouuid.NewV4().String() + "@localhost",
Password: webAuthUser,
IsActive: true,
}
if err = db.CreateUser(u); err != nil {
// FIXME: should I create a system notice?
log.Error("Failed to create user: %v", err)
user, err = db.Users.Create(
ctx.Req.Context(),
webAuthUser,
gouuid.NewV4().String()+"@localhost",
db.CreateUserOptions{
Activated: true,
},
)
if err != nil {
log.Error("Failed to create user %q: %v", webAuthUser, err)
return nil, false, false
} else {
return u, false, false
}
}
}
return u, false, false
return user, false, false
}
}

View File

@ -245,7 +245,9 @@ func (t *Team) RemoveRepository(repoID int64) error {
return sess.Commit()
}
var reservedTeamNames = []string{"new"}
var reservedTeamNames = map[string]struct{}{
"new": {},
}
// IsUsableTeamName return an error if given name is a reserved name or pattern.
func IsUsableTeamName(name string) error {

View File

@ -1081,8 +1081,14 @@ func initRepository(e Engine, repoPath string, doer *User, repo *Repository, opt
}
var (
reservedRepoNames = []string{".", ".."}
reservedRepoPatterns = []string{"*.git", "*.wiki"}
reservedRepoNames = map[string]struct{}{
".": {},
"..": {},
}
reservedRepoPatterns = []string{
"*.git",
"*.wiki",
}
)
// isRepoNameAllowed return an error if given name is a reserved name or pattern for repositories.

View File

@ -13,7 +13,6 @@ import (
"path/filepath"
"strings"
"time"
"unicode/utf8"
"github.com/unknwon/com"
log "unknwon.dev/clog/v2"
@ -59,115 +58,6 @@ func (u *User) getOrganizationCount(e Engine) (int64, error) {
return e.Where("uid=?", u.ID).Count(new(OrgUser))
}
var (
reservedUsernames = []string{"-", "explore", "create", "assets", "css", "img", "js", "less", "plugins", "debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new", ".", ".."}
reservedUserPatterns = []string{"*.keys"}
)
type ErrNameNotAllowed struct {
args errutil.Args
}
func IsErrNameNotAllowed(err error) bool {
_, ok := err.(ErrNameNotAllowed)
return ok
}
func (err ErrNameNotAllowed) Value() string {
val, ok := err.args["name"].(string)
if ok {
return val
}
val, ok = err.args["pattern"].(string)
if ok {
return val
}
return "<value not found>"
}
func (err ErrNameNotAllowed) Error() string {
return fmt.Sprintf("name is not allowed: %v", err.args)
}
// isNameAllowed checks if name is reserved or pattern of name is not allowed
// based on given reserved names and patterns.
// Names are exact match, patterns can be prefix or suffix match with placeholder '*'.
func isNameAllowed(names, patterns []string, name string) error {
name = strings.TrimSpace(strings.ToLower(name))
if utf8.RuneCountInString(name) == 0 {
return ErrNameNotAllowed{args: errutil.Args{"reason": "empty name"}}
}
for i := range names {
if name == names[i] {
return ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "name": name}}
}
}
for _, pat := range patterns {
if pat[0] == '*' && strings.HasSuffix(name, pat[1:]) ||
(pat[len(pat)-1] == '*' && strings.HasPrefix(name, pat[:len(pat)-1])) {
return ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "pattern": pat}}
}
}
return nil
}
// isUsernameAllowed return an error if given name is a reserved name or pattern for users.
func isUsernameAllowed(name string) error {
return isNameAllowed(reservedUsernames, reservedUserPatterns, name)
}
// CreateUser creates record of a new user.
//
// Deprecated: Use Users.Create instead.
func CreateUser(u *User) (err error) {
if err = isUsernameAllowed(u.Name); err != nil {
return err
}
if Users.IsUsernameUsed(context.TODO(), u.Name) {
return ErrUserAlreadyExist{args: errutil.Args{"name": u.Name}}
}
u.Email = strings.ToLower(u.Email)
isExist, err := IsEmailUsed(u.Email)
if err != nil {
return err
} else if isExist {
return ErrEmailAlreadyUsed{args: errutil.Args{"email": u.Email}}
}
u.LowerName = strings.ToLower(u.Name)
u.AvatarEmail = u.Email
u.Avatar = tool.HashEmail(u.AvatarEmail)
if u.Rands, err = userutil.RandomSalt(); err != nil {
return err
}
if u.Salt, err = userutil.RandomSalt(); err != nil {
return err
}
u.Password = userutil.EncodePassword(u.Password, u.Salt)
u.MaxRepoCreation = -1
sess := x.NewSession()
defer sess.Close()
if err = sess.Begin(); err != nil {
return err
}
if _, err = sess.Insert(u); err != nil {
return err
} else if err = os.MkdirAll(UserPath(u.Name), os.ModePerm); err != nil {
return err
}
return sess.Commit()
}
func countUsers(e Engine) int64 {
count, _ := e.Where("type=0").Count(new(User))
return count

View File

@ -10,6 +10,7 @@ import (
"os"
"strings"
"time"
"unicode/utf8"
"github.com/go-macaron/binding"
api "github.com/gogs/go-gogs-client"
@ -234,12 +235,20 @@ func (db *users) Create(ctx context.Context, username, email string, opts Create
}
if db.IsUsernameUsed(ctx, username) {
return nil, ErrUserAlreadyExist{args: errutil.Args{"name": username}}
return nil, ErrUserAlreadyExist{
args: errutil.Args{
"name": username,
},
}
}
_, err = db.GetByEmail(ctx, email)
if err == nil {
return nil, ErrEmailAlreadyUsed{args: errutil.Args{"email": email}}
return nil, ErrEmailAlreadyUsed{
args: errutil.Args{
"email": email,
},
}
} else if !IsErrUserNotExist(err) {
return nil, err
}
@ -257,7 +266,7 @@ func (db *users) Create(ctx context.Context, username, email string, opts Create
MaxRepoCreation: -1,
IsActive: opts.Activated,
IsAdmin: opts.Admin,
Avatar: cryptoutil.MD5(email),
Avatar: cryptoutil.MD5(email), // Gravatar URL uses the MD5 hash of the email, see https://en.gravatar.com/site/implement/hash/
AvatarEmail: email,
}
@ -579,16 +588,6 @@ func (u *User) DisplayName() string {
return u.Name
}
// NewGhostUser creates and returns a fake user for people who has deleted their
// accounts.
func NewGhostUser() *User {
return &User{
ID: -1,
Name: "Ghost",
LowerName: "ghost",
}
}
// HomeURLPath returns the URL path to the user or organization home page.
//
// TODO(unknwon): This is also used in templates, which should be fixed by
@ -693,3 +692,119 @@ func (u *User) GetOrganizationCount() (int64, error) {
func (u *User) ShortName(length int) string {
return strutil.Ellipsis(u.Name, length)
}
// NewGhostUser creates and returns a fake user for people who has deleted their
// accounts.
//
// TODO: Once migrated to unknwon.dev/i18n, pass in the `i18n.Locale` to
// translate the text to local language.
func NewGhostUser() *User {
return &User{
ID: -1,
Name: "Ghost",
LowerName: "ghost",
}
}
var (
reservedUsernames = map[string]struct{}{
"-": {},
"explore": {},
"create": {},
"assets": {},
"css": {},
"img": {},
"js": {},
"less": {},
"plugins": {},
"debug": {},
"raw": {},
"install": {},
"api": {},
"avatar": {},
"user": {},
"org": {},
"help": {},
"stars": {},
"issues": {},
"pulls": {},
"commits": {},
"repo": {},
"template": {},
"admin": {},
"new": {},
".": {},
"..": {},
}
reservedUsernamePatterns = []string{"*.keys"}
)
type ErrNameNotAllowed struct {
args errutil.Args
}
func IsErrNameNotAllowed(err error) bool {
_, ok := err.(ErrNameNotAllowed)
return ok
}
func (err ErrNameNotAllowed) Value() string {
val, ok := err.args["name"].(string)
if ok {
return val
}
val, ok = err.args["pattern"].(string)
if ok {
return val
}
return "<value not found>"
}
func (err ErrNameNotAllowed) Error() string {
return fmt.Sprintf("name is not allowed: %v", err.args)
}
// isNameAllowed checks if the name is reserved or pattern of the name is not
// allowed based on given reserved names and patterns. Names are exact match,
// patterns can be prefix or suffix match with the wildcard ("*").
func isNameAllowed(names map[string]struct{}, patterns []string, name string) error {
name = strings.TrimSpace(strings.ToLower(name))
if utf8.RuneCountInString(name) == 0 {
return ErrNameNotAllowed{
args: errutil.Args{
"reason": "empty name",
},
}
}
if _, ok := names[name]; ok {
return ErrNameNotAllowed{
args: errutil.Args{
"reason": "reserved",
"name": name,
},
}
}
for _, pattern := range patterns {
if pattern[0] == '*' && strings.HasSuffix(name, pattern[1:]) ||
(pattern[len(pattern)-1] == '*' && strings.HasPrefix(name, pattern[:len(pattern)-1])) {
return ErrNameNotAllowed{
args: errutil.Args{
"reason": "reserved",
"pattern": pattern,
},
}
}
}
return nil
}
// isUsernameAllowed returns ErrNameNotAllowed if the given name or pattern of
// the name is not allowed as a username.
func isUsernameAllowed(name string) error {
return isNameAllowed(reservedUsernames, reservedUsernamePatterns, name)
}

View File

@ -8,6 +8,7 @@ import (
"context"
"fmt"
"os"
"strings"
"testing"
"time"
@ -211,7 +212,10 @@ func usersAuthenticate(t *testing.T, db *users) {
func usersCreate(t *testing.T, db *users) {
ctx := context.Background()
alice, err := db.Create(ctx, "alice", "alice@example.com",
alice, err := db.Create(
ctx,
"alice",
"alice@example.com",
CreateUserOptions{
Activated: true,
},
@ -220,19 +224,32 @@ func usersCreate(t *testing.T, db *users) {
t.Run("name not allowed", func(t *testing.T) {
_, err := db.Create(ctx, "-", "", CreateUserOptions{})
wantErr := ErrNameNotAllowed{args: errutil.Args{"reason": "reserved", "name": "-"}}
wantErr := ErrNameNotAllowed{
args: errutil.Args{
"reason": "reserved",
"name": "-",
},
}
assert.Equal(t, wantErr, err)
})
t.Run("name already exists", func(t *testing.T) {
_, err := db.Create(ctx, alice.Name, "", CreateUserOptions{})
wantErr := ErrUserAlreadyExist{args: errutil.Args{"name": alice.Name}}
wantErr := ErrUserAlreadyExist{
args: errutil.Args{
"name": alice.Name,
},
}
assert.Equal(t, wantErr, err)
})
t.Run("email already exists", func(t *testing.T) {
_, err := db.Create(ctx, "bob", alice.Email, CreateUserOptions{})
wantErr := ErrEmailAlreadyUsed{args: errutil.Args{"email": alice.Email}}
wantErr := ErrEmailAlreadyUsed{
args: errutil.Args{
"email": alice.Email,
},
}
assert.Equal(t, wantErr, err)
})
@ -495,3 +512,18 @@ func usersUseCustomAvatar(t *testing.T, db *users) {
require.NoError(t, err)
assert.True(t, alice.UseCustomAvatar)
}
func TestIsUsernameAllowed(t *testing.T) {
for name := range reservedUsernames {
t.Run(name, func(t *testing.T) {
assert.True(t, IsErrNameNotAllowed(isUsernameAllowed(name)))
})
}
for _, pattern := range reservedUsernamePatterns {
t.Run(pattern, func(t *testing.T) {
username := strings.ReplaceAll(pattern, "*", "alice")
assert.True(t, IsErrNameNotAllowed(isUsernameAllowed(username)))
})
}
}

View File

@ -5,6 +5,7 @@
package admin
import (
"strconv"
"strings"
"github.com/unknwon/com"
@ -77,22 +78,20 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
return
}
u := &db.User{
Name: f.UserName,
Email: f.Email,
Password: f.Password,
IsActive: true,
createUserOpts := db.CreateUserOptions{
Password: f.Password,
Activated: true,
}
if len(f.LoginType) > 0 {
fields := strings.Split(f.LoginType, "-")
if len(fields) == 2 {
u.LoginSource = com.StrTo(fields[1]).MustInt64()
u.LoginName = f.LoginName
createUserOpts.LoginSource, _ = strconv.ParseInt(fields[1], 10, 64)
createUserOpts.LoginName = f.LoginName
}
}
if err := db.CreateUser(u); err != nil {
user, err := db.Users.Create(c.Req.Context(), f.UserName, f.Email, createUserOpts)
if err != nil {
switch {
case db.IsErrUserAlreadyExist(err):
c.Data["Err_UserName"] = true
@ -108,15 +107,15 @@ func NewUserPost(c *context.Context, f form.AdminCrateUser) {
}
return
}
log.Trace("Account created by admin (%s): %s", c.User.Name, u.Name)
log.Trace("Account %q created by admin %q", user.Name, c.User.Name)
// Send email notification.
if f.SendNotify && conf.Email.Enabled {
email.SendRegisterNotifyMail(c.Context, db.NewMailerUser(u))
email.SendRegisterNotifyMail(c.Context, db.NewMailerUser(user))
}
c.Flash.Success(c.Tr("admin.users.new_success", u.Name))
c.Redirect(conf.Server.Subpath + "/admin/users/" + com.ToStr(u.ID))
c.Flash.Success(c.Tr("admin.users.new_success", user.Name))
c.Redirect(conf.Server.Subpath + "/admin/users/" + strconv.FormatInt(user.ID, 10))
}
func prepareUserInfo(c *context.Context) *db.User {

View File

@ -18,12 +18,12 @@ import (
"gogs.io/gogs/internal/userutil"
)
func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginName string) {
func parseLoginSource(c *context.APIContext, sourceID int64) {
if sourceID == 0 {
return
}
source, err := db.LoginSources.GetByID(c.Req.Context(), sourceID)
_, err := db.LoginSources.GetByID(c.Req.Context(), sourceID)
if err != nil {
if db.IsErrLoginSourceNotExist(err) {
c.ErrorStatus(http.StatusUnprocessableEntity, err)
@ -32,26 +32,27 @@ func parseLoginSource(c *context.APIContext, u *db.User, sourceID int64, loginNa
}
return
}
u.LoginSource = source.ID
u.LoginName = loginName
}
func CreateUser(c *context.APIContext, form api.CreateUserOption) {
u := &db.User{
Name: form.Username,
FullName: form.FullName,
Email: form.Email,
Password: form.Password,
IsActive: true,
}
parseLoginSource(c, u, form.SourceID, form.LoginName)
parseLoginSource(c, form.SourceID)
if c.Written() {
return
}
if err := db.CreateUser(u); err != nil {
user, err := db.Users.Create(
c.Req.Context(),
form.Username,
form.Email,
db.CreateUserOptions{
FullName: form.FullName,
Password: form.Password,
LoginSource: form.SourceID,
LoginName: form.LoginName,
Activated: true,
},
)
if err != nil {
if db.IsErrUserAlreadyExist(err) ||
db.IsErrEmailAlreadyUsed(err) ||
db.IsErrNameNotAllowed(err) {
@ -61,14 +62,14 @@ func CreateUser(c *context.APIContext, form api.CreateUserOption) {
}
return
}
log.Trace("Account created by admin %q: %s", c.User.Name, u.Name)
log.Trace("Account %q created by admin %q", user.Name, c.User.Name)
// Send email notification.
if form.SendNotify && conf.Email.Enabled {
email.SendRegisterNotifyMail(c.Context.Context, db.NewMailerUser(u))
email.SendRegisterNotifyMail(c.Context.Context, db.NewMailerUser(user))
}
c.JSON(http.StatusCreated, u.APIFormat())
c.JSON(http.StatusCreated, user.APIFormat())
}
func EditUser(c *context.APIContext, form api.EditUserOption) {
@ -77,7 +78,7 @@ func EditUser(c *context.APIContext, form api.EditUserOption) {
return
}
parseLoginSource(c, u, form.SourceID, form.LoginName)
parseLoginSource(c, form.SourceID)
if c.Written() {
return
}
@ -92,6 +93,7 @@ func EditUser(c *context.APIContext, form api.EditUserOption) {
u.Password = userutil.EncodePassword(u.Password, u.Salt)
}
u.LoginSource = form.SourceID
u.LoginName = form.LoginName
u.FullName = form.FullName
u.Email = form.Email

View File

@ -387,27 +387,35 @@ func InstallPost(c *context.Context, f form.Install) {
// Create admin account
if len(f.AdminName) > 0 {
u := &db.User{
Name: f.AdminName,
Email: f.AdminEmail,
Password: f.AdminPasswd,
IsAdmin: true,
IsActive: true,
}
if err := db.CreateUser(u); err != nil {
user, err := db.Users.Create(
c.Req.Context(),
f.AdminName,
f.AdminEmail,
db.CreateUserOptions{
Password: f.AdminPasswd,
Activated: true,
Admin: true,
},
)
if err != nil {
if !db.IsErrUserAlreadyExist(err) {
conf.Security.InstallLock = false
c.FormErr("AdminName", "AdminEmail")
c.RenderWithErr(c.Tr("install.invalid_admin_setting", err), INSTALL, &f)
return
}
log.Info("Admin account already exist")
u, _ = db.GetUserByName(u.Name)
user, err = db.Users.GetByUsername(c.Req.Context(), f.AdminName)
if err != nil {
c.Error(err, "get user by name")
return
}
}
// Auto-login for admin
_ = c.Session.Set("uid", u.ID)
_ = c.Session.Set("uname", u.Name)
_ = c.Session.Set("uid", user.ID)
_ = c.Session.Set("uname", user.Name)
}
log.Info("First-time run install finished!")

View File

@ -6,6 +6,7 @@ package user
import (
"fmt"
"net/http"
"net/url"
"github.com/go-macaron/captcha"
@ -311,7 +312,7 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
c.Data["EnableCaptcha"] = conf.Auth.EnableRegistrationCaptcha
if conf.Auth.DisableRegistration {
c.Status(403)
c.Status(http.StatusForbidden)
return
}
@ -332,13 +333,16 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
return
}
u := &db.User{
Name: f.UserName,
Email: f.Email,
Password: f.Password,
IsActive: !conf.Auth.RequireEmailConfirmation,
}
if err := db.CreateUser(u); err != nil {
user, err := db.Users.Create(
c.Req.Context(),
f.UserName,
f.Email,
db.CreateUserOptions{
Password: f.Password,
Activated: !conf.Auth.RequireEmailConfirmation,
},
)
if err != nil {
switch {
case db.IsErrUserAlreadyExist(err):
c.FormErr("UserName")
@ -354,27 +358,27 @@ func SignUpPost(c *context.Context, cpt *captcha.Captcha, f form.Register) {
}
return
}
log.Trace("Account created: %s", u.Name)
log.Trace("Account created: %s", user.Name)
// Auto-set admin for the only user.
if db.CountUsers() == 1 {
u.IsAdmin = true
u.IsActive = true
if err := db.UpdateUser(u); err != nil {
user.IsAdmin = true
user.IsActive = true
if err := db.UpdateUser(user); err != nil {
c.Error(err, "update user")
return
}
}
// Send confirmation email.
if conf.Auth.RequireEmailConfirmation && u.ID > 1 {
email.SendActivateAccountMail(c.Context, db.NewMailerUser(u))
if conf.Auth.RequireEmailConfirmation && user.ID > 1 {
email.SendActivateAccountMail(c.Context, db.NewMailerUser(user))
c.Data["IsSendRegisterMail"] = true
c.Data["Email"] = u.Email
c.Data["Email"] = user.Email
c.Data["Hours"] = conf.Auth.ActivateCodeLives / 60
c.Success(ACTIVATE)
if err := c.Cache.Put(userutil.MailResendCacheKey(u.ID), 1, 180); err != nil {
if err := c.Cache.Put(userutil.MailResendCacheKey(user.ID), 1, 180); err != nil {
log.Error("Failed to put cache key 'mail resend': %v", err)
}
return