1// Copyright 2014 The Gogs Authors. All rights reserved. 2// Copyright 2019 The Gitea Authors. All rights reserved. 3// Use of this source code is governed by a MIT-style 4// license that can be found in the LICENSE file. 5 6package user 7 8import ( 9 "context" 10 "crypto/sha256" 11 "crypto/subtle" 12 "encoding/hex" 13 "fmt" 14 "net/url" 15 "os" 16 "path/filepath" 17 "strings" 18 "time" 19 20 _ "image/jpeg" // Needed for jpeg support 21 22 "code.gitea.io/gitea/models/auth" 23 "code.gitea.io/gitea/models/db" 24 "code.gitea.io/gitea/modules/auth/openid" 25 "code.gitea.io/gitea/modules/base" 26 "code.gitea.io/gitea/modules/git" 27 "code.gitea.io/gitea/modules/log" 28 "code.gitea.io/gitea/modules/setting" 29 "code.gitea.io/gitea/modules/structs" 30 "code.gitea.io/gitea/modules/timeutil" 31 "code.gitea.io/gitea/modules/util" 32 33 "golang.org/x/crypto/argon2" 34 "golang.org/x/crypto/bcrypt" 35 "golang.org/x/crypto/pbkdf2" 36 "golang.org/x/crypto/scrypt" 37 "xorm.io/builder" 38) 39 40// UserType defines the user type 41type UserType int //revive:disable-line:exported 42 43const ( 44 // UserTypeIndividual defines an individual user 45 UserTypeIndividual UserType = iota // Historic reason to make it starts at 0. 46 47 // UserTypeOrganization defines an organization 48 UserTypeOrganization 49) 50 51const ( 52 algoBcrypt = "bcrypt" 53 algoScrypt = "scrypt" 54 algoArgon2 = "argon2" 55 algoPbkdf2 = "pbkdf2" 56) 57 58// AvailableHashAlgorithms represents the available password hashing algorithms 59var AvailableHashAlgorithms = []string{ 60 algoPbkdf2, 61 algoArgon2, 62 algoScrypt, 63 algoBcrypt, 64} 65 66const ( 67 // EmailNotificationsEnabled indicates that the user would like to receive all email notifications 68 EmailNotificationsEnabled = "enabled" 69 // EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned. 70 EmailNotificationsOnMention = "onmention" 71 // EmailNotificationsDisabled indicates that the user would not like to be notified via email. 72 EmailNotificationsDisabled = "disabled" 73) 74 75// User represents the object of individual and member of organization. 76type User struct { 77 ID int64 `xorm:"pk autoincr"` 78 LowerName string `xorm:"UNIQUE NOT NULL"` 79 Name string `xorm:"UNIQUE NOT NULL"` 80 FullName string 81 // Email is the primary email address (to be used for communication) 82 Email string `xorm:"NOT NULL"` 83 KeepEmailPrivate bool 84 EmailNotificationsPreference string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'enabled'"` 85 Passwd string `xorm:"NOT NULL"` 86 PasswdHashAlgo string `xorm:"NOT NULL DEFAULT 'argon2'"` 87 88 // MustChangePassword is an attribute that determines if a user 89 // is to change his/her password after registration. 90 MustChangePassword bool `xorm:"NOT NULL DEFAULT false"` 91 92 LoginType auth.Type 93 LoginSource int64 `xorm:"NOT NULL DEFAULT 0"` 94 LoginName string 95 Type UserType 96 Location string 97 Website string 98 Rands string `xorm:"VARCHAR(32)"` 99 Salt string `xorm:"VARCHAR(32)"` 100 Language string `xorm:"VARCHAR(5)"` 101 Description string 102 103 CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` 104 UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` 105 LastLoginUnix timeutil.TimeStamp `xorm:"INDEX"` 106 107 // Remember visibility choice for convenience, true for private 108 LastRepoVisibility bool 109 // Maximum repository creation limit, -1 means use global default 110 MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1"` 111 112 // IsActive true: primary email is activated, user can access Web UI and Git SSH. 113 // false: an inactive user can only log in Web UI for account operations (ex: activate the account by email), no other access. 114 IsActive bool `xorm:"INDEX"` 115 // the user is a Gitea admin, who can access all repositories and the admin pages. 116 IsAdmin bool 117 // true: the user is only allowed to see organizations/repositories that they has explicit rights to. 118 // (ex: in private Gitea instances user won't be allowed to see even organizations/repositories that are set as public) 119 IsRestricted bool `xorm:"NOT NULL DEFAULT false"` 120 121 AllowGitHook bool 122 AllowImportLocal bool // Allow migrate repository by local path 123 AllowCreateOrganization bool `xorm:"DEFAULT true"` 124 125 // true: the user is not allowed to log in Web UI. Git/SSH access could still be allowed (please refer to Git/SSH access related code/documents) 126 ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"` 127 128 // Avatar 129 Avatar string `xorm:"VARCHAR(2048) NOT NULL"` 130 AvatarEmail string `xorm:"NOT NULL"` 131 UseCustomAvatar bool 132 133 // Counters 134 NumFollowers int 135 NumFollowing int `xorm:"NOT NULL DEFAULT 0"` 136 NumStars int 137 NumRepos int 138 139 // For organization 140 NumTeams int 141 NumMembers int 142 Visibility structs.VisibleType `xorm:"NOT NULL DEFAULT 0"` 143 RepoAdminChangeTeamAccess bool `xorm:"NOT NULL DEFAULT false"` 144 145 // Preferences 146 DiffViewStyle string `xorm:"NOT NULL DEFAULT ''"` 147 Theme string `xorm:"NOT NULL DEFAULT ''"` 148 KeepActivityPrivate bool `xorm:"NOT NULL DEFAULT false"` 149} 150 151func init() { 152 db.RegisterModel(new(User)) 153} 154 155// SearchOrganizationsOptions options to filter organizations 156type SearchOrganizationsOptions struct { 157 db.ListOptions 158 All bool 159} 160 161// ColorFormat writes a colored string to identify this struct 162func (u *User) ColorFormat(s fmt.State) { 163 if u == nil { 164 log.ColorFprintf(s, "%d:%s", 165 log.NewColoredIDValue(0), 166 log.NewColoredValue("<nil>")) 167 return 168 } 169 log.ColorFprintf(s, "%d:%s", 170 log.NewColoredIDValue(u.ID), 171 log.NewColoredValue(u.Name)) 172} 173 174// BeforeUpdate is invoked from XORM before updating this object. 175func (u *User) BeforeUpdate() { 176 if u.MaxRepoCreation < -1 { 177 u.MaxRepoCreation = -1 178 } 179 180 // Organization does not need email 181 u.Email = strings.ToLower(u.Email) 182 if !u.IsOrganization() { 183 if len(u.AvatarEmail) == 0 { 184 u.AvatarEmail = u.Email 185 } 186 } 187 188 u.LowerName = strings.ToLower(u.Name) 189 u.Location = base.TruncateString(u.Location, 255) 190 u.Website = base.TruncateString(u.Website, 255) 191 u.Description = base.TruncateString(u.Description, 255) 192} 193 194// AfterLoad is invoked from XORM after filling all the fields of this object. 195func (u *User) AfterLoad() { 196 if u.Theme == "" { 197 u.Theme = setting.UI.DefaultTheme 198 } 199} 200 201// SetLastLogin set time to last login 202func (u *User) SetLastLogin() { 203 u.LastLoginUnix = timeutil.TimeStampNow() 204} 205 206// UpdateUserDiffViewStyle updates the users diff view style 207func UpdateUserDiffViewStyle(u *User, style string) error { 208 u.DiffViewStyle = style 209 return UpdateUserCols(db.DefaultContext, u, "diff_view_style") 210} 211 212// UpdateUserTheme updates a users' theme irrespective of the site wide theme 213func UpdateUserTheme(u *User, themeName string) error { 214 u.Theme = themeName 215 return UpdateUserCols(db.DefaultContext, u, "theme") 216} 217 218// GetEmail returns an noreply email, if the user has set to keep his 219// email address private, otherwise the primary email address. 220func (u *User) GetEmail() string { 221 if u.KeepEmailPrivate { 222 return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress) 223 } 224 return u.Email 225} 226 227// GetAllUsers returns a slice of all individual users found in DB. 228func GetAllUsers() ([]*User, error) { 229 users := make([]*User, 0) 230 return users, db.GetEngine(db.DefaultContext).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) 231} 232 233// IsLocal returns true if user login type is LoginPlain. 234func (u *User) IsLocal() bool { 235 return u.LoginType <= auth.Plain 236} 237 238// IsOAuth2 returns true if user login type is LoginOAuth2. 239func (u *User) IsOAuth2() bool { 240 return u.LoginType == auth.OAuth2 241} 242 243// MaxCreationLimit returns the number of repositories a user is allowed to create 244func (u *User) MaxCreationLimit() int { 245 if u.MaxRepoCreation <= -1 { 246 return setting.Repository.MaxCreationLimit 247 } 248 return u.MaxRepoCreation 249} 250 251// CanCreateRepo returns if user login can create a repository 252// NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised 253func (u *User) CanCreateRepo() bool { 254 if u.IsAdmin { 255 return true 256 } 257 if u.MaxRepoCreation <= -1 { 258 if setting.Repository.MaxCreationLimit <= -1 { 259 return true 260 } 261 return u.NumRepos < setting.Repository.MaxCreationLimit 262 } 263 return u.NumRepos < u.MaxRepoCreation 264} 265 266// CanCreateOrganization returns true if user can create organisation. 267func (u *User) CanCreateOrganization() bool { 268 return u.IsAdmin || (u.AllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation) 269} 270 271// CanEditGitHook returns true if user can edit Git hooks. 272func (u *User) CanEditGitHook() bool { 273 return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook) 274} 275 276// CanImportLocal returns true if user can migrate repository by local path. 277func (u *User) CanImportLocal() bool { 278 if !setting.ImportLocalPaths || u == nil { 279 return false 280 } 281 return u.IsAdmin || u.AllowImportLocal 282} 283 284// DashboardLink returns the user dashboard page link. 285func (u *User) DashboardLink() string { 286 if u.IsOrganization() { 287 return u.OrganisationLink() + "/dashboard" 288 } 289 return setting.AppSubURL + "/" 290} 291 292// HomeLink returns the user or organization home page link. 293func (u *User) HomeLink() string { 294 return setting.AppSubURL + "/" + url.PathEscape(u.Name) 295} 296 297// HTMLURL returns the user or organization's full link. 298func (u *User) HTMLURL() string { 299 return setting.AppURL + url.PathEscape(u.Name) 300} 301 302// OrganisationLink returns the organization sub page link. 303func (u *User) OrganisationLink() string { 304 return setting.AppSubURL + "/org/" + url.PathEscape(u.Name) 305} 306 307// GenerateEmailActivateCode generates an activate code based on user information and given e-mail. 308func (u *User) GenerateEmailActivateCode(email string) string { 309 code := base.CreateTimeLimitCode( 310 fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands), 311 setting.Service.ActiveCodeLives, nil) 312 313 // Add tail hex username 314 code += hex.EncodeToString([]byte(u.LowerName)) 315 return code 316} 317 318// GetUserFollowers returns range of user's followers. 319func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) { 320 sess := db.GetEngine(db.DefaultContext). 321 Where("follow.follow_id=?", u.ID). 322 Join("LEFT", "follow", "`user`.id=follow.user_id") 323 324 if listOptions.Page != 0 { 325 sess = db.SetSessionPagination(sess, &listOptions) 326 327 users := make([]*User, 0, listOptions.PageSize) 328 return users, sess.Find(&users) 329 } 330 331 users := make([]*User, 0, 8) 332 return users, sess.Find(&users) 333} 334 335// GetUserFollowing returns range of user's following. 336func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) { 337 sess := db.GetEngine(db.DefaultContext). 338 Where("follow.user_id=?", u.ID). 339 Join("LEFT", "follow", "`user`.id=follow.follow_id") 340 341 if listOptions.Page != 0 { 342 sess = db.SetSessionPagination(sess, &listOptions) 343 344 users := make([]*User, 0, listOptions.PageSize) 345 return users, sess.Find(&users) 346 } 347 348 users := make([]*User, 0, 8) 349 return users, sess.Find(&users) 350} 351 352// NewGitSig generates and returns the signature of given user. 353func (u *User) NewGitSig() *git.Signature { 354 return &git.Signature{ 355 Name: u.GitName(), 356 Email: u.GetEmail(), 357 When: time.Now(), 358 } 359} 360 361func hashPassword(passwd, salt, algo string) (string, error) { 362 var tempPasswd []byte 363 var saltBytes []byte 364 365 // There are two formats for the Salt value: 366 // * The new format is a (32+)-byte hex-encoded string 367 // * The old format was a 10-byte binary format 368 // We have to tolerate both here but Authenticate should 369 // regenerate the Salt following a successful validation. 370 if len(salt) == 10 { 371 saltBytes = []byte(salt) 372 } else { 373 var err error 374 saltBytes, err = hex.DecodeString(salt) 375 if err != nil { 376 return "", err 377 } 378 } 379 380 switch algo { 381 case algoBcrypt: 382 tempPasswd, _ = bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost) 383 return string(tempPasswd), nil 384 case algoScrypt: 385 tempPasswd, _ = scrypt.Key([]byte(passwd), saltBytes, 65536, 16, 2, 50) 386 case algoArgon2: 387 tempPasswd = argon2.IDKey([]byte(passwd), saltBytes, 2, 65536, 8, 50) 388 case algoPbkdf2: 389 fallthrough 390 default: 391 tempPasswd = pbkdf2.Key([]byte(passwd), saltBytes, 10000, 50, sha256.New) 392 } 393 394 return fmt.Sprintf("%x", tempPasswd), nil 395} 396 397// SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO 398// change passwd, salt and passwd_hash_algo fields 399func (u *User) SetPassword(passwd string) (err error) { 400 if len(passwd) == 0 { 401 u.Passwd = "" 402 u.Salt = "" 403 u.PasswdHashAlgo = "" 404 return nil 405 } 406 407 if u.Salt, err = GetUserSalt(); err != nil { 408 return err 409 } 410 if u.Passwd, err = hashPassword(passwd, u.Salt, setting.PasswordHashAlgo); err != nil { 411 return err 412 } 413 u.PasswdHashAlgo = setting.PasswordHashAlgo 414 415 return nil 416} 417 418// ValidatePassword checks if given password matches the one belongs to the user. 419func (u *User) ValidatePassword(passwd string) bool { 420 tempHash, err := hashPassword(passwd, u.Salt, u.PasswdHashAlgo) 421 if err != nil { 422 return false 423 } 424 425 if u.PasswdHashAlgo != algoBcrypt && subtle.ConstantTimeCompare([]byte(u.Passwd), []byte(tempHash)) == 1 { 426 return true 427 } 428 if u.PasswdHashAlgo == algoBcrypt && bcrypt.CompareHashAndPassword([]byte(u.Passwd), []byte(passwd)) == nil { 429 return true 430 } 431 return false 432} 433 434// IsPasswordSet checks if the password is set or left empty 435func (u *User) IsPasswordSet() bool { 436 return len(u.Passwd) != 0 437} 438 439// IsOrganization returns true if user is actually a organization. 440func (u *User) IsOrganization() bool { 441 return u.Type == UserTypeOrganization 442} 443 444// DisplayName returns full name if it's not empty, 445// returns username otherwise. 446func (u *User) DisplayName() string { 447 trimmed := strings.TrimSpace(u.FullName) 448 if len(trimmed) > 0 { 449 return trimmed 450 } 451 return u.Name 452} 453 454// GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, 455// returns username otherwise. 456func (u *User) GetDisplayName() string { 457 if setting.UI.DefaultShowFullName { 458 trimmed := strings.TrimSpace(u.FullName) 459 if len(trimmed) > 0 { 460 return trimmed 461 } 462 } 463 return u.Name 464} 465 466func gitSafeName(name string) string { 467 return strings.TrimSpace(strings.NewReplacer("\n", "", "<", "", ">", "").Replace(name)) 468} 469 470// GitName returns a git safe name 471func (u *User) GitName() string { 472 gitName := gitSafeName(u.FullName) 473 if len(gitName) > 0 { 474 return gitName 475 } 476 // Although u.Name should be safe if created in our system 477 // LDAP users may have bad names 478 gitName = gitSafeName(u.Name) 479 if len(gitName) > 0 { 480 return gitName 481 } 482 // Totally pathological name so it's got to be: 483 return fmt.Sprintf("user-%d", u.ID) 484} 485 486// ShortName ellipses username to length 487func (u *User) ShortName(length int) string { 488 return base.EllipsisString(u.Name, length) 489} 490 491// IsMailable checks if a user is eligible 492// to receive emails. 493func (u *User) IsMailable() bool { 494 return u.IsActive 495} 496 497// EmailNotifications returns the User's email notification preference 498func (u *User) EmailNotifications() string { 499 return u.EmailNotificationsPreference 500} 501 502// SetEmailNotifications sets the user's email notification preference 503func SetEmailNotifications(u *User, set string) error { 504 u.EmailNotificationsPreference = set 505 if err := UpdateUserCols(db.DefaultContext, u, "email_notifications_preference"); err != nil { 506 log.Error("SetEmailNotifications: %v", err) 507 return err 508 } 509 return nil 510} 511 512func isUserExist(e db.Engine, uid int64, name string) (bool, error) { 513 if len(name) == 0 { 514 return false, nil 515 } 516 return e. 517 Where("id!=?", uid). 518 Get(&User{LowerName: strings.ToLower(name)}) 519} 520 521// IsUserExist checks if given user name exist, 522// the user name should be noncased unique. 523// If uid is presented, then check will rule out that one, 524// it is used when update a user name in settings page. 525func IsUserExist(uid int64, name string) (bool, error) { 526 return isUserExist(db.GetEngine(db.DefaultContext), uid, name) 527} 528 529// Note: As of the beginning of 2022, it is recommended to use at least 530// 64 bits of salt, but NIST is already recommending to use to 128 bits. 531// (16 bytes = 16 * 8 = 128 bits) 532const SaltByteLength = 16 533 534// GetUserSalt returns a random user salt token. 535func GetUserSalt() (string, error) { 536 rBytes, err := util.RandomBytes(SaltByteLength) 537 if err != nil { 538 return "", err 539 } 540 // Returns a 32 bytes long string. 541 return hex.EncodeToString(rBytes), nil 542} 543 544// NewGhostUser creates and returns a fake user for someone has deleted his/her account. 545func NewGhostUser() *User { 546 return &User{ 547 ID: -1, 548 Name: "Ghost", 549 LowerName: "ghost", 550 } 551} 552 553// NewReplaceUser creates and returns a fake user for external user 554func NewReplaceUser(name string) *User { 555 return &User{ 556 ID: -1, 557 Name: name, 558 LowerName: strings.ToLower(name), 559 } 560} 561 562// IsGhost check if user is fake user for a deleted account 563func (u *User) IsGhost() bool { 564 if u == nil { 565 return false 566 } 567 return u.ID == -1 && u.Name == "Ghost" 568} 569 570var ( 571 reservedUsernames = []string{ 572 ".", 573 "..", 574 ".well-known", 575 "admin", 576 "api", 577 "assets", 578 "attachments", 579 "avatars", 580 "captcha", 581 "commits", 582 "debug", 583 "error", 584 "explore", 585 "favicon.ico", 586 "ghost", 587 "help", 588 "install", 589 "issues", 590 "less", 591 "login", 592 "manifest.json", 593 "metrics", 594 "milestones", 595 "new", 596 "notifications", 597 "org", 598 "plugins", 599 "pulls", 600 "raw", 601 "repo", 602 "robots.txt", 603 "search", 604 "serviceworker.js", 605 "stars", 606 "template", 607 "user", 608 } 609 610 reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom"} 611) 612 613// IsUsableUsername returns an error when a username is reserved 614func IsUsableUsername(name string) error { 615 // Validate username make sure it satisfies requirement. 616 if db.AlphaDashDotPattern.MatchString(name) { 617 // Note: usually this error is normally caught up earlier in the UI 618 return db.ErrNameCharsNotAllowed{Name: name} 619 } 620 return db.IsUsableName(reservedUsernames, reservedUserPatterns, name) 621} 622 623// CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation 624type CreateUserOverwriteOptions struct { 625 Visibility structs.VisibleType 626} 627 628// CreateUser creates record of a new user. 629func CreateUser(u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { 630 if err = IsUsableUsername(u.Name); err != nil { 631 return err 632 } 633 634 // set system defaults 635 u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate 636 u.Visibility = setting.Service.DefaultUserVisibilityMode 637 u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation 638 u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification 639 u.MaxRepoCreation = -1 640 u.Theme = setting.UI.DefaultTheme 641 642 // overwrite defaults if set 643 if len(overwriteDefault) != 0 && overwriteDefault[0] != nil { 644 u.Visibility = overwriteDefault[0].Visibility 645 } 646 647 // validate data 648 if err := validateUser(u); err != nil { 649 return err 650 } 651 652 if err := ValidateEmail(u.Email); err != nil { 653 return err 654 } 655 656 ctx, committer, err := db.TxContext() 657 if err != nil { 658 return err 659 } 660 defer committer.Close() 661 662 sess := db.GetEngine(ctx) 663 664 isExist, err := isUserExist(sess, 0, u.Name) 665 if err != nil { 666 return err 667 } else if isExist { 668 return ErrUserAlreadyExist{u.Name} 669 } 670 671 isExist, err = IsEmailUsed(ctx, u.Email) 672 if err != nil { 673 return err 674 } else if isExist { 675 return ErrEmailAlreadyUsed{ 676 Email: u.Email, 677 } 678 } 679 680 // prepare for database 681 682 u.LowerName = strings.ToLower(u.Name) 683 u.AvatarEmail = u.Email 684 if u.Rands, err = GetUserSalt(); err != nil { 685 return err 686 } 687 if err = u.SetPassword(u.Passwd); err != nil { 688 return err 689 } 690 691 // save changes to database 692 693 if err = DeleteUserRedirect(ctx, u.Name); err != nil { 694 return err 695 } 696 697 if err = db.Insert(ctx, u); err != nil { 698 return err 699 } 700 701 // insert email address 702 if err := db.Insert(ctx, &EmailAddress{ 703 UID: u.ID, 704 Email: u.Email, 705 LowerEmail: strings.ToLower(u.Email), 706 IsActivated: u.IsActive, 707 IsPrimary: true, 708 }); err != nil { 709 return err 710 } 711 712 return committer.Commit() 713} 714 715func countUsers(e db.Engine) int64 { 716 count, _ := e. 717 Where("type=0"). 718 Count(new(User)) 719 return count 720} 721 722// CountUsers returns number of users. 723func CountUsers() int64 { 724 return countUsers(db.GetEngine(db.DefaultContext)) 725} 726 727// GetVerifyUser get user by verify code 728func GetVerifyUser(code string) (user *User) { 729 if len(code) <= base.TimeLimitCodeLength { 730 return nil 731 } 732 733 // use tail hex username query user 734 hexStr := code[base.TimeLimitCodeLength:] 735 if b, err := hex.DecodeString(hexStr); err == nil { 736 if user, err = GetUserByName(string(b)); user != nil { 737 return user 738 } 739 log.Error("user.getVerifyUser: %v", err) 740 } 741 742 return nil 743} 744 745// VerifyUserActiveCode verifies active code when active account 746func VerifyUserActiveCode(code string) (user *User) { 747 minutes := setting.Service.ActiveCodeLives 748 749 if user = GetVerifyUser(code); user != nil { 750 // time limit code 751 prefix := code[:base.TimeLimitCodeLength] 752 data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands) 753 754 if base.VerifyTimeLimitCode(data, minutes, prefix) { 755 return user 756 } 757 } 758 return nil 759} 760 761// ChangeUserName changes all corresponding setting from old user name to new one. 762func ChangeUserName(u *User, newUserName string) (err error) { 763 oldUserName := u.Name 764 if err = IsUsableUsername(newUserName); err != nil { 765 return err 766 } 767 768 ctx, committer, err := db.TxContext() 769 if err != nil { 770 return err 771 } 772 defer committer.Close() 773 sess := db.GetEngine(ctx) 774 775 isExist, err := isUserExist(sess, 0, newUserName) 776 if err != nil { 777 return err 778 } else if isExist { 779 return ErrUserAlreadyExist{newUserName} 780 } 781 782 if _, err = sess.Exec("UPDATE `repository` SET owner_name=? WHERE owner_name=?", newUserName, oldUserName); err != nil { 783 return fmt.Errorf("Change repo owner name: %v", err) 784 } 785 786 // Do not fail if directory does not exist 787 if err = util.Rename(UserPath(oldUserName), UserPath(newUserName)); err != nil && !os.IsNotExist(err) { 788 return fmt.Errorf("Rename user directory: %v", err) 789 } 790 791 if err = NewUserRedirect(ctx, u.ID, oldUserName, newUserName); err != nil { 792 return err 793 } 794 795 if err = committer.Commit(); err != nil { 796 if err2 := util.Rename(UserPath(newUserName), UserPath(oldUserName)); err2 != nil && !os.IsNotExist(err2) { 797 log.Critical("Unable to rollback directory change during failed username change from: %s to: %s. DB Error: %v. Filesystem Error: %v", oldUserName, newUserName, err, err2) 798 return fmt.Errorf("failed to rollback directory change during failed username change from: %s to: %s. DB Error: %w. Filesystem Error: %v", oldUserName, newUserName, err, err2) 799 } 800 return err 801 } 802 803 return nil 804} 805 806// checkDupEmail checks whether there are the same email with the user 807func checkDupEmail(e db.Engine, u *User) error { 808 u.Email = strings.ToLower(u.Email) 809 has, err := e. 810 Where("id!=?", u.ID). 811 And("type=?", u.Type). 812 And("email=?", u.Email). 813 Get(new(User)) 814 if err != nil { 815 return err 816 } else if has { 817 return ErrEmailAlreadyUsed{ 818 Email: u.Email, 819 } 820 } 821 return nil 822} 823 824// validateUser check if user is valid to insert / update into database 825func validateUser(u *User) error { 826 if !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(u.Visibility) && !u.IsOrganization() { 827 return fmt.Errorf("visibility Mode not allowed: %s", u.Visibility.String()) 828 } 829 830 u.Email = strings.ToLower(u.Email) 831 return ValidateEmail(u.Email) 832} 833 834func updateUser(ctx context.Context, u *User, changePrimaryEmail bool, cols ...string) error { 835 err := validateUser(u) 836 if err != nil { 837 return err 838 } 839 840 e := db.GetEngine(ctx) 841 842 if changePrimaryEmail { 843 var emailAddress EmailAddress 844 has, err := e.Where("lower_email=?", strings.ToLower(u.Email)).Get(&emailAddress) 845 if err != nil { 846 return err 847 } 848 if !has { 849 // 1. Update old primary email 850 if _, err = e.Where("uid=? AND is_primary=?", u.ID, true).Cols("is_primary").Update(&EmailAddress{ 851 IsPrimary: false, 852 }); err != nil { 853 return err 854 } 855 856 emailAddress.Email = u.Email 857 emailAddress.UID = u.ID 858 emailAddress.IsActivated = true 859 emailAddress.IsPrimary = true 860 if _, err := e.Insert(&emailAddress); err != nil { 861 return err 862 } 863 } else if _, err := e.ID(emailAddress.ID).Cols("is_primary").Update(&EmailAddress{ 864 IsPrimary: true, 865 }); err != nil { 866 return err 867 } 868 } else if !u.IsOrganization() { // check if primary email in email_address table 869 primaryEmailExist, err := e.Where("uid=? AND is_primary=?", u.ID, true).Exist(&EmailAddress{}) 870 if err != nil { 871 return err 872 } 873 874 if !primaryEmailExist { 875 if _, err = e.Insert(&EmailAddress{ 876 Email: u.Email, 877 UID: u.ID, 878 IsActivated: true, 879 IsPrimary: true, 880 }); err != nil { 881 return err 882 } 883 } 884 } 885 886 if len(cols) == 0 { 887 _, err = e.ID(u.ID).AllCols().Update(u) 888 } else { 889 _, err = e.ID(u.ID).Cols(cols...).Update(u) 890 } 891 return err 892} 893 894// UpdateUser updates user's information. 895func UpdateUser(u *User, emailChanged bool, cols ...string) error { 896 return updateUser(db.DefaultContext, u, emailChanged, cols...) 897} 898 899// UpdateUserCols update user according special columns 900func UpdateUserCols(ctx context.Context, u *User, cols ...string) error { 901 return updateUserCols(db.GetEngine(ctx), u, cols...) 902} 903 904// UpdateUserColsEngine update user according special columns 905func UpdateUserColsEngine(e db.Engine, u *User, cols ...string) error { 906 return updateUserCols(e, u, cols...) 907} 908 909func updateUserCols(e db.Engine, u *User, cols ...string) error { 910 if err := validateUser(u); err != nil { 911 return err 912 } 913 914 _, err := e.ID(u.ID).Cols(cols...).Update(u) 915 return err 916} 917 918// UpdateUserSetting updates user's settings. 919func UpdateUserSetting(u *User) (err error) { 920 ctx, committer, err := db.TxContext() 921 if err != nil { 922 return err 923 } 924 defer committer.Close() 925 926 if !u.IsOrganization() { 927 if err = checkDupEmail(db.GetEngine(ctx), u); err != nil { 928 return err 929 } 930 } 931 if err = updateUser(ctx, u, false); err != nil { 932 return err 933 } 934 return committer.Commit() 935} 936 937// GetInactiveUsers gets all inactive users 938func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) { 939 var cond builder.Cond = builder.Eq{"is_active": false} 940 941 if olderThan > 0 { 942 cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()}) 943 } 944 945 users := make([]*User, 0, 10) 946 return users, db.GetEngine(ctx). 947 Where(cond). 948 Find(&users) 949} 950 951// UserPath returns the path absolute path of user repositories. 952func UserPath(userName string) string { //revive:disable-line:exported 953 return filepath.Join(setting.RepoRootPath, strings.ToLower(userName)) 954} 955 956// GetUserByIDEngine returns the user object by given ID if exists. 957func GetUserByIDEngine(e db.Engine, id int64) (*User, error) { 958 u := new(User) 959 has, err := e.ID(id).Get(u) 960 if err != nil { 961 return nil, err 962 } else if !has { 963 return nil, ErrUserNotExist{id, "", 0} 964 } 965 return u, nil 966} 967 968// GetUserByID returns the user object by given ID if exists. 969func GetUserByID(id int64) (*User, error) { 970 return GetUserByIDCtx(db.DefaultContext, id) 971} 972 973// GetUserByIDCtx returns the user object by given ID if exists. 974func GetUserByIDCtx(ctx context.Context, id int64) (*User, error) { 975 return GetUserByIDEngine(db.GetEngine(ctx), id) 976} 977 978// GetUserByName returns user by given name. 979func GetUserByName(name string) (*User, error) { 980 return GetUserByNameCtx(db.DefaultContext, name) 981} 982 983// GetUserByNameCtx returns user by given name. 984func GetUserByNameCtx(ctx context.Context, name string) (*User, error) { 985 if len(name) == 0 { 986 return nil, ErrUserNotExist{0, name, 0} 987 } 988 u := &User{LowerName: strings.ToLower(name)} 989 has, err := db.GetEngine(ctx).Get(u) 990 if err != nil { 991 return nil, err 992 } else if !has { 993 return nil, ErrUserNotExist{0, name, 0} 994 } 995 return u, nil 996} 997 998// GetUserEmailsByNames returns a list of e-mails corresponds to names of users 999// that have their email notifications set to enabled or onmention. 1000func GetUserEmailsByNames(names []string) []string { 1001 return getUserEmailsByNames(db.DefaultContext, names) 1002} 1003 1004func getUserEmailsByNames(ctx context.Context, names []string) []string { 1005 mails := make([]string, 0, len(names)) 1006 for _, name := range names { 1007 u, err := GetUserByNameCtx(ctx, name) 1008 if err != nil { 1009 continue 1010 } 1011 if u.IsMailable() && u.EmailNotifications() != EmailNotificationsDisabled { 1012 mails = append(mails, u.Email) 1013 } 1014 } 1015 return mails 1016} 1017 1018// GetMaileableUsersByIDs gets users from ids, but only if they can receive mails 1019func GetMaileableUsersByIDs(ids []int64, isMention bool) ([]*User, error) { 1020 if len(ids) == 0 { 1021 return nil, nil 1022 } 1023 ous := make([]*User, 0, len(ids)) 1024 1025 if isMention { 1026 return ous, db.GetEngine(db.DefaultContext).In("id", ids). 1027 Where("`type` = ?", UserTypeIndividual). 1028 And("`prohibit_login` = ?", false). 1029 And("`is_active` = ?", true). 1030 And("`email_notifications_preference` IN ( ?, ?)", EmailNotificationsEnabled, EmailNotificationsOnMention). 1031 Find(&ous) 1032 } 1033 1034 return ous, db.GetEngine(db.DefaultContext).In("id", ids). 1035 Where("`type` = ?", UserTypeIndividual). 1036 And("`prohibit_login` = ?", false). 1037 And("`is_active` = ?", true). 1038 And("`email_notifications_preference` = ?", EmailNotificationsEnabled). 1039 Find(&ous) 1040} 1041 1042// GetUserNamesByIDs returns usernames for all resolved users from a list of Ids. 1043func GetUserNamesByIDs(ids []int64) ([]string, error) { 1044 unames := make([]string, 0, len(ids)) 1045 err := db.GetEngine(db.DefaultContext).In("id", ids). 1046 Table("user"). 1047 Asc("name"). 1048 Cols("name"). 1049 Find(&unames) 1050 return unames, err 1051} 1052 1053// GetUserIDsByNames returns a slice of ids corresponds to names. 1054func GetUserIDsByNames(names []string, ignoreNonExistent bool) ([]int64, error) { 1055 ids := make([]int64, 0, len(names)) 1056 for _, name := range names { 1057 u, err := GetUserByName(name) 1058 if err != nil { 1059 if ignoreNonExistent { 1060 continue 1061 } else { 1062 return nil, err 1063 } 1064 } 1065 ids = append(ids, u.ID) 1066 } 1067 return ids, nil 1068} 1069 1070// GetUsersBySource returns a list of Users for a login source 1071func GetUsersBySource(s *auth.Source) ([]*User, error) { 1072 var users []*User 1073 err := db.GetEngine(db.DefaultContext).Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users) 1074 return users, err 1075} 1076 1077// UserCommit represents a commit with validation of user. 1078type UserCommit struct { //revive:disable-line:exported 1079 User *User 1080 *git.Commit 1081} 1082 1083// ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user. 1084func ValidateCommitWithEmail(c *git.Commit) *User { 1085 if c.Author == nil { 1086 return nil 1087 } 1088 u, err := GetUserByEmail(c.Author.Email) 1089 if err != nil { 1090 return nil 1091 } 1092 return u 1093} 1094 1095// ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. 1096func ValidateCommitsWithEmails(oldCommits []*git.Commit) []*UserCommit { 1097 var ( 1098 emails = make(map[string]*User) 1099 newCommits = make([]*UserCommit, 0, len(oldCommits)) 1100 ) 1101 for _, c := range oldCommits { 1102 var u *User 1103 if c.Author != nil { 1104 if v, ok := emails[c.Author.Email]; !ok { 1105 u, _ = GetUserByEmail(c.Author.Email) 1106 emails[c.Author.Email] = u 1107 } else { 1108 u = v 1109 } 1110 } 1111 1112 newCommits = append(newCommits, &UserCommit{ 1113 User: u, 1114 Commit: c, 1115 }) 1116 } 1117 return newCommits 1118} 1119 1120// GetUserByEmail returns the user object by given e-mail if exists. 1121func GetUserByEmail(email string) (*User, error) { 1122 return GetUserByEmailContext(db.DefaultContext, email) 1123} 1124 1125// GetUserByEmailContext returns the user object by given e-mail if exists with db context 1126func GetUserByEmailContext(ctx context.Context, email string) (*User, error) { 1127 if len(email) == 0 { 1128 return nil, ErrUserNotExist{0, email, 0} 1129 } 1130 1131 email = strings.ToLower(email) 1132 // Otherwise, check in alternative list for activated email addresses 1133 emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true} 1134 has, err := db.GetEngine(ctx).Get(emailAddress) 1135 if err != nil { 1136 return nil, err 1137 } 1138 if has { 1139 return GetUserByIDCtx(ctx, emailAddress.UID) 1140 } 1141 1142 // Finally, if email address is the protected email address: 1143 if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) { 1144 username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) 1145 user := &User{} 1146 has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user) 1147 if err != nil { 1148 return nil, err 1149 } 1150 if has { 1151 return user, nil 1152 } 1153 } 1154 1155 return nil, ErrUserNotExist{0, email, 0} 1156} 1157 1158// GetUser checks if a user already exists 1159func GetUser(user *User) (bool, error) { 1160 return db.GetEngine(db.DefaultContext).Get(user) 1161} 1162 1163// GetUserByOpenID returns the user object by given OpenID if exists. 1164func GetUserByOpenID(uri string) (*User, error) { 1165 if len(uri) == 0 { 1166 return nil, ErrUserNotExist{0, uri, 0} 1167 } 1168 1169 uri, err := openid.Normalize(uri) 1170 if err != nil { 1171 return nil, err 1172 } 1173 1174 log.Trace("Normalized OpenID URI: " + uri) 1175 1176 // Otherwise, check in openid table 1177 oid := &UserOpenID{} 1178 has, err := db.GetEngine(db.DefaultContext).Where("uri=?", uri).Get(oid) 1179 if err != nil { 1180 return nil, err 1181 } 1182 if has { 1183 return GetUserByID(oid.UID) 1184 } 1185 1186 return nil, ErrUserNotExist{0, uri, 0} 1187} 1188 1189// GetAdminUser returns the first administrator 1190func GetAdminUser() (*User, error) { 1191 var admin User 1192 has, err := db.GetEngine(db.DefaultContext).Where("is_admin=?", true).Get(&admin) 1193 if err != nil { 1194 return nil, err 1195 } else if !has { 1196 return nil, ErrUserNotExist{} 1197 } 1198 1199 return &admin, nil 1200} 1201