code.gitea.io/gitea@v1.22.3/models/user/user.go (about) 1 // Copyright 2014 The Gogs Authors. All rights reserved. 2 // Copyright 2019 The Gitea Authors. All rights reserved. 3 // SPDX-License-Identifier: MIT 4 5 package user 6 7 import ( 8 "context" 9 "encoding/hex" 10 "fmt" 11 "net/url" 12 "path/filepath" 13 "regexp" 14 "strings" 15 "time" 16 "unicode" 17 18 _ "image/jpeg" // Needed for jpeg support 19 20 "code.gitea.io/gitea/models/auth" 21 "code.gitea.io/gitea/models/db" 22 "code.gitea.io/gitea/modules/auth/openid" 23 "code.gitea.io/gitea/modules/auth/password/hash" 24 "code.gitea.io/gitea/modules/base" 25 "code.gitea.io/gitea/modules/container" 26 "code.gitea.io/gitea/modules/git" 27 "code.gitea.io/gitea/modules/log" 28 "code.gitea.io/gitea/modules/optional" 29 "code.gitea.io/gitea/modules/setting" 30 "code.gitea.io/gitea/modules/structs" 31 "code.gitea.io/gitea/modules/timeutil" 32 "code.gitea.io/gitea/modules/util" 33 "code.gitea.io/gitea/modules/validation" 34 35 "golang.org/x/text/runes" 36 "golang.org/x/text/transform" 37 "golang.org/x/text/unicode/norm" 38 "xorm.io/builder" 39 ) 40 41 // UserType defines the user type 42 type UserType int //revive:disable-line:exported 43 44 const ( 45 // UserTypeIndividual defines an individual user 46 UserTypeIndividual UserType = iota // Historic reason to make it starts at 0. 47 48 // UserTypeOrganization defines an organization 49 UserTypeOrganization 50 51 // UserTypeUserReserved reserves a (non-existing) user, i.e. to prevent a spam user from re-registering after being deleted, or to reserve the name until the user is actually created later on 52 UserTypeUserReserved 53 54 // UserTypeOrganizationReserved reserves a (non-existing) organization, to be used in combination with UserTypeUserReserved 55 UserTypeOrganizationReserved 56 57 // UserTypeBot defines a bot user 58 UserTypeBot 59 60 // UserTypeRemoteUser defines a remote user for federated users 61 UserTypeRemoteUser 62 ) 63 64 const ( 65 // EmailNotificationsEnabled indicates that the user would like to receive all email notifications except your own 66 EmailNotificationsEnabled = "enabled" 67 // EmailNotificationsOnMention indicates that the user would like to be notified via email when mentioned. 68 EmailNotificationsOnMention = "onmention" 69 // EmailNotificationsDisabled indicates that the user would not like to be notified via email. 70 EmailNotificationsDisabled = "disabled" 71 // EmailNotificationsAndYourOwn indicates that the user would like to receive all email notifications and your own 72 EmailNotificationsAndYourOwn = "andyourown" 73 ) 74 75 // User represents the object of individual and member of organization. 76 type 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 their 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 151 func init() { 152 db.RegisterModel(new(User)) 153 } 154 155 // SearchOrganizationsOptions options to filter organizations 156 type SearchOrganizationsOptions struct { 157 db.ListOptions 158 All bool 159 } 160 161 func (u *User) LogString() string { 162 if u == nil { 163 return "<User nil>" 164 } 165 return fmt.Sprintf("<User %d:%s>", u.ID, u.Name) 166 } 167 168 // BeforeUpdate is invoked from XORM before updating this object. 169 func (u *User) BeforeUpdate() { 170 if u.MaxRepoCreation < -1 { 171 u.MaxRepoCreation = -1 172 } 173 174 // Organization does not need email 175 u.Email = strings.ToLower(u.Email) 176 if !u.IsOrganization() { 177 if len(u.AvatarEmail) == 0 { 178 u.AvatarEmail = u.Email 179 } 180 } 181 182 u.LowerName = strings.ToLower(u.Name) 183 u.Location = base.TruncateString(u.Location, 255) 184 u.Website = base.TruncateString(u.Website, 255) 185 u.Description = base.TruncateString(u.Description, 255) 186 } 187 188 // AfterLoad is invoked from XORM after filling all the fields of this object. 189 func (u *User) AfterLoad() { 190 if u.Theme == "" { 191 u.Theme = setting.UI.DefaultTheme 192 } 193 } 194 195 // SetLastLogin set time to last login 196 func (u *User) SetLastLogin() { 197 u.LastLoginUnix = timeutil.TimeStampNow() 198 } 199 200 // GetPlaceholderEmail returns an noreply email 201 func (u *User) GetPlaceholderEmail() string { 202 return fmt.Sprintf("%s@%s", u.LowerName, setting.Service.NoReplyAddress) 203 } 204 205 // GetEmail returns an noreply email, if the user has set to keep his 206 // email address private, otherwise the primary email address. 207 func (u *User) GetEmail() string { 208 if u.KeepEmailPrivate { 209 return u.GetPlaceholderEmail() 210 } 211 return u.Email 212 } 213 214 // GetAllUsers returns a slice of all individual users found in DB. 215 func GetAllUsers(ctx context.Context) ([]*User, error) { 216 users := make([]*User, 0) 217 return users, db.GetEngine(ctx).OrderBy("id").Where("type = ?", UserTypeIndividual).Find(&users) 218 } 219 220 // IsLocal returns true if user login type is LoginPlain. 221 func (u *User) IsLocal() bool { 222 return u.LoginType <= auth.Plain 223 } 224 225 // IsOAuth2 returns true if user login type is LoginOAuth2. 226 func (u *User) IsOAuth2() bool { 227 return u.LoginType == auth.OAuth2 228 } 229 230 // MaxCreationLimit returns the number of repositories a user is allowed to create 231 func (u *User) MaxCreationLimit() int { 232 if u.MaxRepoCreation <= -1 { 233 return setting.Repository.MaxCreationLimit 234 } 235 return u.MaxRepoCreation 236 } 237 238 // CanCreateRepo returns if user login can create a repository 239 // NOTE: functions calling this assume a failure due to repository count limit; if new checks are added, those functions should be revised 240 func (u *User) CanCreateRepo() bool { 241 if u.IsAdmin { 242 return true 243 } 244 if u.MaxRepoCreation <= -1 { 245 if setting.Repository.MaxCreationLimit <= -1 { 246 return true 247 } 248 return u.NumRepos < setting.Repository.MaxCreationLimit 249 } 250 return u.NumRepos < u.MaxRepoCreation 251 } 252 253 // CanCreateOrganization returns true if user can create organisation. 254 func (u *User) CanCreateOrganization() bool { 255 return u.IsAdmin || (u.AllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation) 256 } 257 258 // CanEditGitHook returns true if user can edit Git hooks. 259 func (u *User) CanEditGitHook() bool { 260 return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook) 261 } 262 263 // CanForkRepo returns if user login can fork a repository 264 // It checks especially that the user can create repos, and potentially more 265 func (u *User) CanForkRepo() bool { 266 if setting.Repository.AllowForkWithoutMaximumLimit { 267 return true 268 } 269 return u.CanCreateRepo() 270 } 271 272 // CanImportLocal returns true if user can migrate repository by local path. 273 func (u *User) CanImportLocal() bool { 274 if !setting.ImportLocalPaths || u == nil { 275 return false 276 } 277 return u.IsAdmin || u.AllowImportLocal 278 } 279 280 // DashboardLink returns the user dashboard page link. 281 func (u *User) DashboardLink() string { 282 if u.IsOrganization() { 283 return u.OrganisationLink() + "/dashboard" 284 } 285 return setting.AppSubURL + "/" 286 } 287 288 // HomeLink returns the user or organization home page link. 289 func (u *User) HomeLink() string { 290 return setting.AppSubURL + "/" + url.PathEscape(u.Name) 291 } 292 293 // HTMLURL returns the user or organization's full link. 294 func (u *User) HTMLURL() string { 295 return setting.AppURL + url.PathEscape(u.Name) 296 } 297 298 // OrganisationLink returns the organization sub page link. 299 func (u *User) OrganisationLink() string { 300 return setting.AppSubURL + "/org/" + url.PathEscape(u.Name) 301 } 302 303 // GenerateEmailActivateCode generates an activate code based on user information and given e-mail. 304 func (u *User) GenerateEmailActivateCode(email string) string { 305 code := base.CreateTimeLimitCode( 306 fmt.Sprintf("%d%s%s%s%s", u.ID, email, u.LowerName, u.Passwd, u.Rands), 307 setting.Service.ActiveCodeLives, time.Now(), nil) 308 309 // Add tail hex username 310 code += hex.EncodeToString([]byte(u.LowerName)) 311 return code 312 } 313 314 // GetUserFollowers returns range of user's followers. 315 func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { 316 sess := db.GetEngine(ctx). 317 Select("`user`.*"). 318 Join("LEFT", "follow", "`user`.id=follow.user_id"). 319 Where("follow.follow_id=?", u.ID). 320 And("`user`.type=?", UserTypeIndividual). 321 And(isUserVisibleToViewerCond(viewer)) 322 323 if listOptions.Page != 0 { 324 sess = db.SetSessionPagination(sess, &listOptions) 325 326 users := make([]*User, 0, listOptions.PageSize) 327 count, err := sess.FindAndCount(&users) 328 return users, count, err 329 } 330 331 users := make([]*User, 0, 8) 332 count, err := sess.FindAndCount(&users) 333 return users, count, err 334 } 335 336 // GetUserFollowing returns range of user's following. 337 func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) { 338 sess := db.GetEngine(ctx). 339 Select("`user`.*"). 340 Join("LEFT", "follow", "`user`.id=follow.follow_id"). 341 Where("follow.user_id=?", u.ID). 342 And("`user`.type IN (?, ?)", UserTypeIndividual, UserTypeOrganization). 343 And(isUserVisibleToViewerCond(viewer)) 344 345 if listOptions.Page != 0 { 346 sess = db.SetSessionPagination(sess, &listOptions) 347 348 users := make([]*User, 0, listOptions.PageSize) 349 count, err := sess.FindAndCount(&users) 350 return users, count, err 351 } 352 353 users := make([]*User, 0, 8) 354 count, err := sess.FindAndCount(&users) 355 return users, count, err 356 } 357 358 // NewGitSig generates and returns the signature of given user. 359 func (u *User) NewGitSig() *git.Signature { 360 return &git.Signature{ 361 Name: u.GitName(), 362 Email: u.GetEmail(), 363 When: time.Now(), 364 } 365 } 366 367 // SetPassword hashes a password using the algorithm defined in the config value of PASSWORD_HASH_ALGO 368 // change passwd, salt and passwd_hash_algo fields 369 func (u *User) SetPassword(passwd string) (err error) { 370 if u.Salt, err = GetUserSalt(); err != nil { 371 return err 372 } 373 if u.Passwd, err = hash.Parse(setting.PasswordHashAlgo).Hash(passwd, u.Salt); err != nil { 374 return err 375 } 376 u.PasswdHashAlgo = setting.PasswordHashAlgo 377 378 return nil 379 } 380 381 // ValidatePassword checks if the given password matches the one belonging to the user. 382 func (u *User) ValidatePassword(passwd string) bool { 383 return hash.Parse(u.PasswdHashAlgo).VerifyPassword(passwd, u.Passwd, u.Salt) 384 } 385 386 // IsPasswordSet checks if the password is set or left empty 387 func (u *User) IsPasswordSet() bool { 388 return len(u.Passwd) != 0 389 } 390 391 // IsOrganization returns true if user is actually a organization. 392 func (u *User) IsOrganization() bool { 393 return u.Type == UserTypeOrganization 394 } 395 396 // IsIndividual returns true if user is actually a individual user. 397 func (u *User) IsIndividual() bool { 398 return u.Type == UserTypeIndividual 399 } 400 401 func (u *User) IsUser() bool { 402 return u.Type == UserTypeIndividual || u.Type == UserTypeBot 403 } 404 405 // IsBot returns whether or not the user is of type bot 406 func (u *User) IsBot() bool { 407 return u.Type == UserTypeBot 408 } 409 410 // DisplayName returns full name if it's not empty, 411 // returns username otherwise. 412 func (u *User) DisplayName() string { 413 trimmed := strings.TrimSpace(u.FullName) 414 if len(trimmed) > 0 { 415 return trimmed 416 } 417 return u.Name 418 } 419 420 // GetDisplayName returns full name if it's not empty and DEFAULT_SHOW_FULL_NAME is set, 421 // returns username otherwise. 422 func (u *User) GetDisplayName() string { 423 if setting.UI.DefaultShowFullName { 424 trimmed := strings.TrimSpace(u.FullName) 425 if len(trimmed) > 0 { 426 return trimmed 427 } 428 } 429 return u.Name 430 } 431 432 // GetCompleteName returns the full name and username in the form of 433 // "Full Name (username)" if full name is not empty, otherwise it returns 434 // "username". 435 func (u *User) GetCompleteName() string { 436 trimmedFullName := strings.TrimSpace(u.FullName) 437 if len(trimmedFullName) > 0 { 438 return fmt.Sprintf("%s (%s)", trimmedFullName, u.Name) 439 } 440 return u.Name 441 } 442 443 func gitSafeName(name string) string { 444 return strings.TrimSpace(strings.NewReplacer("\n", "", "<", "", ">", "").Replace(name)) 445 } 446 447 // GitName returns a git safe name 448 func (u *User) GitName() string { 449 gitName := gitSafeName(u.FullName) 450 if len(gitName) > 0 { 451 return gitName 452 } 453 // Although u.Name should be safe if created in our system 454 // LDAP users may have bad names 455 gitName = gitSafeName(u.Name) 456 if len(gitName) > 0 { 457 return gitName 458 } 459 // Totally pathological name so it's got to be: 460 return fmt.Sprintf("user-%d", u.ID) 461 } 462 463 // ShortName ellipses username to length 464 func (u *User) ShortName(length int) string { 465 if setting.UI.DefaultShowFullName && len(u.FullName) > 0 { 466 return base.EllipsisString(u.FullName, length) 467 } 468 return base.EllipsisString(u.Name, length) 469 } 470 471 // IsMailable checks if a user is eligible 472 // to receive emails. 473 func (u *User) IsMailable() bool { 474 return u.IsActive 475 } 476 477 // IsUserExist checks if given user name exist, 478 // the user name should be noncased unique. 479 // If uid is presented, then check will rule out that one, 480 // it is used when update a user name in settings page. 481 func IsUserExist(ctx context.Context, uid int64, name string) (bool, error) { 482 if len(name) == 0 { 483 return false, nil 484 } 485 return db.GetEngine(ctx). 486 Where("id!=?", uid). 487 Get(&User{LowerName: strings.ToLower(name)}) 488 } 489 490 // Note: As of the beginning of 2022, it is recommended to use at least 491 // 64 bits of salt, but NIST is already recommending to use to 128 bits. 492 // (16 bytes = 16 * 8 = 128 bits) 493 const SaltByteLength = 16 494 495 // GetUserSalt returns a random user salt token. 496 func GetUserSalt() (string, error) { 497 rBytes, err := util.CryptoRandomBytes(SaltByteLength) 498 if err != nil { 499 return "", err 500 } 501 // Returns a 32 bytes long string. 502 return hex.EncodeToString(rBytes), nil 503 } 504 505 // Note: The set of characters here can safely expand without a breaking change, 506 // but characters removed from this set can cause user account linking to break 507 var ( 508 customCharsReplacement = strings.NewReplacer("Æ", "AE") 509 removeCharsRE = regexp.MustCompile("['`´]") 510 transformDiacritics = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC) 511 replaceCharsHyphenRE = regexp.MustCompile(`[\s~+]`) 512 ) 513 514 // NormalizeUserName only takes the name part if it is an email address, transforms it diacritics to ASCII characters. 515 // It returns a string with the single-quotes removed, and any other non-supported username characters are replaced with a `-` character 516 func NormalizeUserName(s string) (string, error) { 517 s, _, _ = strings.Cut(s, "@") 518 strDiacriticsRemoved, n, err := transform.String(transformDiacritics, customCharsReplacement.Replace(s)) 519 if err != nil { 520 return "", fmt.Errorf("failed to normalize the string of provided username %q at position %d", s, n) 521 } 522 return replaceCharsHyphenRE.ReplaceAllLiteralString(removeCharsRE.ReplaceAllLiteralString(strDiacriticsRemoved, ""), "-"), nil 523 } 524 525 var ( 526 reservedUsernames = []string{ 527 ".", 528 "..", 529 ".well-known", 530 "admin", 531 "api", 532 "assets", 533 "attachments", 534 "avatar", 535 "avatars", 536 "captcha", 537 "commits", 538 "debug", 539 "error", 540 "explore", 541 "favicon.ico", 542 "ghost", 543 "issues", 544 "login", 545 "manifest.json", 546 "metrics", 547 "milestones", 548 "new", 549 "notifications", 550 "org", 551 "pulls", 552 "raw", 553 "repo", 554 "repo-avatars", 555 "robots.txt", 556 "search", 557 "serviceworker.js", 558 "ssh_info", 559 "swagger.v1.json", 560 "user", 561 "v2", 562 "gitea-actions", 563 } 564 565 // DON'T ADD ANY NEW STUFF, WE SOLVE THIS WITH `/user/{obj}` PATHS! 566 reservedUserPatterns = []string{"*.keys", "*.gpg", "*.rss", "*.atom", "*.png"} 567 ) 568 569 // IsUsableUsername returns an error when a username is reserved 570 func IsUsableUsername(name string) error { 571 // Validate username make sure it satisfies requirement. 572 if !validation.IsValidUsername(name) { 573 // Note: usually this error is normally caught up earlier in the UI 574 return db.ErrNameCharsNotAllowed{Name: name} 575 } 576 return db.IsUsableName(reservedUsernames, reservedUserPatterns, name) 577 } 578 579 // CreateUserOverwriteOptions are an optional options who overwrite system defaults on user creation 580 type CreateUserOverwriteOptions struct { 581 KeepEmailPrivate optional.Option[bool] 582 Visibility *structs.VisibleType 583 AllowCreateOrganization optional.Option[bool] 584 EmailNotificationsPreference *string 585 MaxRepoCreation *int 586 Theme *string 587 IsRestricted optional.Option[bool] 588 IsActive optional.Option[bool] 589 } 590 591 // CreateUser creates record of a new user. 592 func CreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { 593 return createUser(ctx, u, false, overwriteDefault...) 594 } 595 596 // AdminCreateUser is used by admins to manually create users 597 func AdminCreateUser(ctx context.Context, u *User, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { 598 return createUser(ctx, u, true, overwriteDefault...) 599 } 600 601 // createUser creates record of a new user. 602 func createUser(ctx context.Context, u *User, createdByAdmin bool, overwriteDefault ...*CreateUserOverwriteOptions) (err error) { 603 if err = IsUsableUsername(u.Name); err != nil { 604 return err 605 } 606 607 // set system defaults 608 u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate 609 u.Visibility = setting.Service.DefaultUserVisibilityMode 610 u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation 611 u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification 612 u.MaxRepoCreation = -1 613 u.Theme = setting.UI.DefaultTheme 614 u.IsRestricted = setting.Service.DefaultUserIsRestricted 615 u.IsActive = !(setting.Service.RegisterEmailConfirm || setting.Service.RegisterManualConfirm) 616 617 // Ensure consistency of the dates. 618 if u.UpdatedUnix < u.CreatedUnix { 619 u.UpdatedUnix = u.CreatedUnix 620 } 621 622 // overwrite defaults if set 623 if len(overwriteDefault) != 0 && overwriteDefault[0] != nil { 624 overwrite := overwriteDefault[0] 625 if overwrite.KeepEmailPrivate.Has() { 626 u.KeepEmailPrivate = overwrite.KeepEmailPrivate.Value() 627 } 628 if overwrite.Visibility != nil { 629 u.Visibility = *overwrite.Visibility 630 } 631 if overwrite.AllowCreateOrganization.Has() { 632 u.AllowCreateOrganization = overwrite.AllowCreateOrganization.Value() 633 } 634 if overwrite.EmailNotificationsPreference != nil { 635 u.EmailNotificationsPreference = *overwrite.EmailNotificationsPreference 636 } 637 if overwrite.MaxRepoCreation != nil { 638 u.MaxRepoCreation = *overwrite.MaxRepoCreation 639 } 640 if overwrite.Theme != nil { 641 u.Theme = *overwrite.Theme 642 } 643 if overwrite.IsRestricted.Has() { 644 u.IsRestricted = overwrite.IsRestricted.Value() 645 } 646 if overwrite.IsActive.Has() { 647 u.IsActive = overwrite.IsActive.Value() 648 } 649 } 650 651 // validate data 652 if err := ValidateUser(u); err != nil { 653 return err 654 } 655 656 if createdByAdmin { 657 if err := ValidateEmailForAdmin(u.Email); err != nil { 658 return err 659 } 660 } else { 661 if err := ValidateEmail(u.Email); err != nil { 662 return err 663 } 664 } 665 666 ctx, committer, err := db.TxContext(ctx) 667 if err != nil { 668 return err 669 } 670 defer committer.Close() 671 672 isExist, err := IsUserExist(ctx, 0, u.Name) 673 if err != nil { 674 return err 675 } else if isExist { 676 return ErrUserAlreadyExist{u.Name} 677 } 678 679 isExist, err = IsEmailUsed(ctx, u.Email) 680 if err != nil { 681 return err 682 } else if isExist { 683 return ErrEmailAlreadyUsed{ 684 Email: u.Email, 685 } 686 } 687 688 // prepare for database 689 690 u.LowerName = strings.ToLower(u.Name) 691 u.AvatarEmail = u.Email 692 if u.Rands, err = GetUserSalt(); err != nil { 693 return err 694 } 695 if u.Passwd != "" { 696 if err = u.SetPassword(u.Passwd); err != nil { 697 return err 698 } 699 } else { 700 u.Salt = "" 701 u.PasswdHashAlgo = "" 702 } 703 704 // save changes to database 705 706 if err = DeleteUserRedirect(ctx, u.Name); err != nil { 707 return err 708 } 709 710 if u.CreatedUnix == 0 { 711 // Caller expects auto-time for creation & update timestamps. 712 err = db.Insert(ctx, u) 713 } else { 714 // Caller sets the timestamps themselves. They are responsible for ensuring 715 // both `CreatedUnix` and `UpdatedUnix` are set appropriately. 716 _, err = db.GetEngine(ctx).NoAutoTime().Insert(u) 717 } 718 if err != nil { 719 return err 720 } 721 722 // insert email address 723 if err := db.Insert(ctx, &EmailAddress{ 724 UID: u.ID, 725 Email: u.Email, 726 LowerEmail: strings.ToLower(u.Email), 727 IsActivated: u.IsActive, 728 IsPrimary: true, 729 }); err != nil { 730 return err 731 } 732 733 return committer.Commit() 734 } 735 736 // IsLastAdminUser check whether user is the last admin 737 func IsLastAdminUser(ctx context.Context, user *User) bool { 738 if user.IsAdmin && CountUsers(ctx, &CountUserFilter{IsAdmin: optional.Some(true)}) <= 1 { 739 return true 740 } 741 return false 742 } 743 744 // CountUserFilter represent optional filters for CountUsers 745 type CountUserFilter struct { 746 LastLoginSince *int64 747 IsAdmin optional.Option[bool] 748 } 749 750 // CountUsers returns number of users. 751 func CountUsers(ctx context.Context, opts *CountUserFilter) int64 { 752 return countUsers(ctx, opts) 753 } 754 755 func countUsers(ctx context.Context, opts *CountUserFilter) int64 { 756 sess := db.GetEngine(ctx) 757 cond := builder.NewCond() 758 cond = cond.And(builder.Eq{"type": UserTypeIndividual}) 759 760 if opts != nil { 761 if opts.LastLoginSince != nil { 762 cond = cond.And(builder.Gte{"last_login_unix": *opts.LastLoginSince}) 763 } 764 765 if opts.IsAdmin.Has() { 766 cond = cond.And(builder.Eq{"is_admin": opts.IsAdmin.Value()}) 767 } 768 } 769 770 count, err := sess.Where(cond).Count(new(User)) 771 if err != nil { 772 log.Error("user.countUsers: %v", err) 773 } 774 775 return count 776 } 777 778 // GetVerifyUser get user by verify code 779 func GetVerifyUser(ctx context.Context, code string) (user *User) { 780 if len(code) <= base.TimeLimitCodeLength { 781 return nil 782 } 783 784 // use tail hex username query user 785 hexStr := code[base.TimeLimitCodeLength:] 786 if b, err := hex.DecodeString(hexStr); err == nil { 787 if user, err = GetUserByName(ctx, string(b)); user != nil { 788 return user 789 } 790 log.Error("user.getVerifyUser: %v", err) 791 } 792 793 return nil 794 } 795 796 // VerifyUserActiveCode verifies active code when active account 797 func VerifyUserActiveCode(ctx context.Context, code string) (user *User) { 798 if user = GetVerifyUser(ctx, code); user != nil { 799 // time limit code 800 prefix := code[:base.TimeLimitCodeLength] 801 data := fmt.Sprintf("%d%s%s%s%s", user.ID, user.Email, user.LowerName, user.Passwd, user.Rands) 802 if base.VerifyTimeLimitCode(time.Now(), data, setting.Service.ActiveCodeLives, prefix) { 803 return user 804 } 805 } 806 return nil 807 } 808 809 // ValidateUser check if user is valid to insert / update into database 810 func ValidateUser(u *User, cols ...string) error { 811 if len(cols) == 0 || util.SliceContainsString(cols, "visibility", true) { 812 if !setting.Service.AllowedUserVisibilityModesSlice.IsAllowedVisibility(u.Visibility) && !u.IsOrganization() { 813 return fmt.Errorf("visibility Mode not allowed: %s", u.Visibility.String()) 814 } 815 } 816 817 return nil 818 } 819 820 // UpdateUserCols update user according special columns 821 func UpdateUserCols(ctx context.Context, u *User, cols ...string) error { 822 if err := ValidateUser(u, cols...); err != nil { 823 return err 824 } 825 826 _, err := db.GetEngine(ctx).ID(u.ID).Cols(cols...).Update(u) 827 return err 828 } 829 830 // GetInactiveUsers gets all inactive users 831 func GetInactiveUsers(ctx context.Context, olderThan time.Duration) ([]*User, error) { 832 var cond builder.Cond = builder.Eq{"is_active": false} 833 834 if olderThan > 0 { 835 cond = cond.And(builder.Lt{"created_unix": time.Now().Add(-olderThan).Unix()}) 836 } 837 838 users := make([]*User, 0, 10) 839 return users, db.GetEngine(ctx). 840 Where(cond). 841 Find(&users) 842 } 843 844 // UserPath returns the path absolute path of user repositories. 845 func UserPath(userName string) string { //revive:disable-line:exported 846 return filepath.Join(setting.RepoRootPath, strings.ToLower(userName)) 847 } 848 849 // GetUserByID returns the user object by given ID if exists. 850 func GetUserByID(ctx context.Context, id int64) (*User, error) { 851 u := new(User) 852 has, err := db.GetEngine(ctx).ID(id).Get(u) 853 if err != nil { 854 return nil, err 855 } else if !has { 856 return nil, ErrUserNotExist{UID: id} 857 } 858 return u, nil 859 } 860 861 // GetUserByIDs returns the user objects by given IDs if exists. 862 func GetUserByIDs(ctx context.Context, ids []int64) ([]*User, error) { 863 users := make([]*User, 0, len(ids)) 864 err := db.GetEngine(ctx).In("id", ids). 865 Table("user"). 866 Find(&users) 867 return users, err 868 } 869 870 // GetPossibleUserByID returns the user if id > 0 or return system usrs if id < 0 871 func GetPossibleUserByID(ctx context.Context, id int64) (*User, error) { 872 switch id { 873 case GhostUserID: 874 return NewGhostUser(), nil 875 case ActionsUserID: 876 return NewActionsUser(), nil 877 case 0: 878 return nil, ErrUserNotExist{} 879 default: 880 return GetUserByID(ctx, id) 881 } 882 } 883 884 // GetPossibleUserByIDs returns the users if id > 0 or return system users if id < 0 885 func GetPossibleUserByIDs(ctx context.Context, ids []int64) ([]*User, error) { 886 uniqueIDs := container.SetOf(ids...) 887 users := make([]*User, 0, len(ids)) 888 _ = uniqueIDs.Remove(0) 889 if uniqueIDs.Remove(GhostUserID) { 890 users = append(users, NewGhostUser()) 891 } 892 if uniqueIDs.Remove(ActionsUserID) { 893 users = append(users, NewActionsUser()) 894 } 895 res, err := GetUserByIDs(ctx, uniqueIDs.Values()) 896 if err != nil { 897 return nil, err 898 } 899 users = append(users, res...) 900 return users, nil 901 } 902 903 // GetUserByNameCtx returns user by given name. 904 func GetUserByName(ctx context.Context, name string) (*User, error) { 905 if len(name) == 0 { 906 return nil, ErrUserNotExist{Name: name} 907 } 908 u := &User{LowerName: strings.ToLower(name), Type: UserTypeIndividual} 909 has, err := db.GetEngine(ctx).Get(u) 910 if err != nil { 911 return nil, err 912 } else if !has { 913 return nil, ErrUserNotExist{Name: name} 914 } 915 return u, nil 916 } 917 918 // GetUserEmailsByNames returns a list of e-mails corresponds to names of users 919 // that have their email notifications set to enabled or onmention. 920 func GetUserEmailsByNames(ctx context.Context, names []string) []string { 921 mails := make([]string, 0, len(names)) 922 for _, name := range names { 923 u, err := GetUserByName(ctx, name) 924 if err != nil { 925 continue 926 } 927 if u.IsMailable() && u.EmailNotificationsPreference != EmailNotificationsDisabled { 928 mails = append(mails, u.Email) 929 } 930 } 931 return mails 932 } 933 934 // GetMaileableUsersByIDs gets users from ids, but only if they can receive mails 935 func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) { 936 if len(ids) == 0 { 937 return nil, nil 938 } 939 ous := make([]*User, 0, len(ids)) 940 941 if isMention { 942 return ous, db.GetEngine(ctx). 943 In("id", ids). 944 Where("`type` = ?", UserTypeIndividual). 945 And("`prohibit_login` = ?", false). 946 And("`is_active` = ?", true). 947 In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsOnMention, EmailNotificationsAndYourOwn). 948 Find(&ous) 949 } 950 951 return ous, db.GetEngine(ctx). 952 In("id", ids). 953 Where("`type` = ?", UserTypeIndividual). 954 And("`prohibit_login` = ?", false). 955 And("`is_active` = ?", true). 956 In("`email_notifications_preference`", EmailNotificationsEnabled, EmailNotificationsAndYourOwn). 957 Find(&ous) 958 } 959 960 // GetUserNamesByIDs returns usernames for all resolved users from a list of Ids. 961 func GetUserNamesByIDs(ctx context.Context, ids []int64) ([]string, error) { 962 unames := make([]string, 0, len(ids)) 963 err := db.GetEngine(ctx).In("id", ids). 964 Table("user"). 965 Asc("name"). 966 Cols("name"). 967 Find(&unames) 968 return unames, err 969 } 970 971 // GetUserNameByID returns username for the id 972 func GetUserNameByID(ctx context.Context, id int64) (string, error) { 973 var name string 974 has, err := db.GetEngine(ctx).Table("user").Where("id = ?", id).Cols("name").Get(&name) 975 if err != nil { 976 return "", err 977 } 978 if has { 979 return name, nil 980 } 981 return "", nil 982 } 983 984 // GetUserIDsByNames returns a slice of ids corresponds to names. 985 func GetUserIDsByNames(ctx context.Context, names []string, ignoreNonExistent bool) ([]int64, error) { 986 ids := make([]int64, 0, len(names)) 987 for _, name := range names { 988 u, err := GetUserByName(ctx, name) 989 if err != nil { 990 if ignoreNonExistent { 991 continue 992 } 993 return nil, err 994 } 995 ids = append(ids, u.ID) 996 } 997 return ids, nil 998 } 999 1000 // GetUsersBySource returns a list of Users for a login source 1001 func GetUsersBySource(ctx context.Context, s *auth.Source) ([]*User, error) { 1002 var users []*User 1003 err := db.GetEngine(ctx).Where("login_type = ? AND login_source = ?", s.Type, s.ID).Find(&users) 1004 return users, err 1005 } 1006 1007 // UserCommit represents a commit with validation of user. 1008 type UserCommit struct { //revive:disable-line:exported 1009 User *User 1010 *git.Commit 1011 } 1012 1013 // ValidateCommitWithEmail check if author's e-mail of commit is corresponding to a user. 1014 func ValidateCommitWithEmail(ctx context.Context, c *git.Commit) *User { 1015 if c.Author == nil { 1016 return nil 1017 } 1018 u, err := GetUserByEmail(ctx, c.Author.Email) 1019 if err != nil { 1020 return nil 1021 } 1022 return u 1023 } 1024 1025 // ValidateCommitsWithEmails checks if authors' e-mails of commits are corresponding to users. 1026 func ValidateCommitsWithEmails(ctx context.Context, oldCommits []*git.Commit) []*UserCommit { 1027 var ( 1028 emails = make(map[string]*User) 1029 newCommits = make([]*UserCommit, 0, len(oldCommits)) 1030 ) 1031 for _, c := range oldCommits { 1032 var u *User 1033 if c.Author != nil { 1034 if v, ok := emails[c.Author.Email]; !ok { 1035 u, _ = GetUserByEmail(ctx, c.Author.Email) 1036 emails[c.Author.Email] = u 1037 } else { 1038 u = v 1039 } 1040 } 1041 1042 newCommits = append(newCommits, &UserCommit{ 1043 User: u, 1044 Commit: c, 1045 }) 1046 } 1047 return newCommits 1048 } 1049 1050 // GetUserByEmail returns the user object by given e-mail if exists. 1051 func GetUserByEmail(ctx context.Context, email string) (*User, error) { 1052 if len(email) == 0 { 1053 return nil, ErrUserNotExist{Name: email} 1054 } 1055 1056 email = strings.ToLower(email) 1057 // Otherwise, check in alternative list for activated email addresses 1058 emailAddress := &EmailAddress{LowerEmail: email, IsActivated: true} 1059 has, err := db.GetEngine(ctx).Get(emailAddress) 1060 if err != nil { 1061 return nil, err 1062 } 1063 if has { 1064 return GetUserByID(ctx, emailAddress.UID) 1065 } 1066 1067 // Finally, if email address is the protected email address: 1068 if strings.HasSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) { 1069 username := strings.TrimSuffix(email, fmt.Sprintf("@%s", setting.Service.NoReplyAddress)) 1070 user := &User{} 1071 has, err := db.GetEngine(ctx).Where("lower_name=?", username).Get(user) 1072 if err != nil { 1073 return nil, err 1074 } 1075 if has { 1076 return user, nil 1077 } 1078 } 1079 1080 return nil, ErrUserNotExist{Name: email} 1081 } 1082 1083 // GetUser checks if a user already exists 1084 func GetUser(ctx context.Context, user *User) (bool, error) { 1085 return db.GetEngine(ctx).Get(user) 1086 } 1087 1088 // GetUserByOpenID returns the user object by given OpenID if exists. 1089 func GetUserByOpenID(ctx context.Context, uri string) (*User, error) { 1090 if len(uri) == 0 { 1091 return nil, ErrUserNotExist{Name: uri} 1092 } 1093 1094 uri, err := openid.Normalize(uri) 1095 if err != nil { 1096 return nil, err 1097 } 1098 1099 log.Trace("Normalized OpenID URI: " + uri) 1100 1101 // Otherwise, check in openid table 1102 oid := &UserOpenID{} 1103 has, err := db.GetEngine(ctx).Where("uri=?", uri).Get(oid) 1104 if err != nil { 1105 return nil, err 1106 } 1107 if has { 1108 return GetUserByID(ctx, oid.UID) 1109 } 1110 1111 return nil, ErrUserNotExist{Name: uri} 1112 } 1113 1114 // GetAdminUser returns the first administrator 1115 func GetAdminUser(ctx context.Context) (*User, error) { 1116 var admin User 1117 has, err := db.GetEngine(ctx). 1118 Where("is_admin=?", true). 1119 Asc("id"). // Reliably get the admin with the lowest ID. 1120 Get(&admin) 1121 if err != nil { 1122 return nil, err 1123 } else if !has { 1124 return nil, ErrUserNotExist{} 1125 } 1126 1127 return &admin, nil 1128 } 1129 1130 func isUserVisibleToViewerCond(viewer *User) builder.Cond { 1131 if viewer != nil && viewer.IsAdmin { 1132 return builder.NewCond() 1133 } 1134 1135 if viewer == nil || viewer.IsRestricted { 1136 return builder.Eq{ 1137 "`user`.visibility": structs.VisibleTypePublic, 1138 } 1139 } 1140 1141 return builder.Neq{ 1142 "`user`.visibility": structs.VisibleTypePrivate, 1143 }.Or( 1144 // viewer self 1145 builder.Eq{"`user`.id": viewer.ID}, 1146 // viewer's following 1147 builder.In("`user`.id", 1148 builder. 1149 Select("`follow`.user_id"). 1150 From("follow"). 1151 Where(builder.Eq{"`follow`.follow_id": viewer.ID})), 1152 // viewer's org user 1153 builder.In("`user`.id", 1154 builder. 1155 Select("`team_user`.uid"). 1156 From("team_user"). 1157 Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id"). 1158 Where(builder.Eq{"`t2`.uid": viewer.ID})), 1159 // viewer's org 1160 builder.In("`user`.id", 1161 builder. 1162 Select("`team_user`.org_id"). 1163 From("team_user"). 1164 Where(builder.Eq{"`team_user`.uid": viewer.ID}))) 1165 } 1166 1167 // IsUserVisibleToViewer check if viewer is able to see user profile 1168 func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { 1169 if viewer != nil && (viewer.IsAdmin || viewer.ID == u.ID) { 1170 return true 1171 } 1172 1173 switch u.Visibility { 1174 case structs.VisibleTypePublic: 1175 return true 1176 case structs.VisibleTypeLimited: 1177 if viewer == nil || viewer.IsRestricted { 1178 return false 1179 } 1180 return true 1181 case structs.VisibleTypePrivate: 1182 if viewer == nil || viewer.IsRestricted { 1183 return false 1184 } 1185 1186 // If they follow - they see each other 1187 follower := IsFollowing(ctx, u.ID, viewer.ID) 1188 if follower { 1189 return true 1190 } 1191 1192 // Now we need to check if they in some organization together 1193 count, err := db.GetEngine(ctx).Table("team_user"). 1194 Where( 1195 builder.And( 1196 builder.Eq{"uid": viewer.ID}, 1197 builder.Or( 1198 builder.Eq{"org_id": u.ID}, 1199 builder.In("org_id", 1200 builder.Select("org_id"). 1201 From("team_user", "t2"). 1202 Where(builder.Eq{"uid": u.ID}))))). 1203 Count() 1204 if err != nil { 1205 return false 1206 } 1207 1208 if count == 0 { 1209 // No common organization 1210 return false 1211 } 1212 1213 // they are in an organization together 1214 return true 1215 } 1216 return false 1217 } 1218 1219 // CountWrongUserType count OrgUser who have wrong type 1220 func CountWrongUserType(ctx context.Context) (int64, error) { 1221 return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Count(new(User)) 1222 } 1223 1224 // FixWrongUserType fix OrgUser who have wrong type 1225 func FixWrongUserType(ctx context.Context) (int64, error) { 1226 return db.GetEngine(ctx).Where(builder.Eq{"type": 0}.And(builder.Neq{"num_teams": 0})).Cols("type").NoAutoTime().Update(&User{Type: 1}) 1227 } 1228 1229 func GetOrderByName() string { 1230 if setting.UI.DefaultShowFullName { 1231 return "full_name, name" 1232 } 1233 return "name" 1234 } 1235 1236 // IsFeatureDisabledWithLoginType checks if a user feature is disabled, taking into account the login type of the 1237 // user if applicable 1238 func IsFeatureDisabledWithLoginType(user *User, feature string) bool { 1239 // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType 1240 return (user != nil && user.LoginType > auth.Plain && setting.Admin.ExternalUserDisableFeatures.Contains(feature)) || 1241 setting.Admin.UserDisabledFeatures.Contains(feature) 1242 } 1243 1244 // DisabledFeaturesWithLoginType returns the set of user features disabled, taking into account the login type 1245 // of the user if applicable 1246 func DisabledFeaturesWithLoginType(user *User) *container.Set[string] { 1247 // NOTE: in the long run it may be better to check the ExternalLoginUser table rather than user.LoginType 1248 if user != nil && user.LoginType > auth.Plain { 1249 return &setting.Admin.ExternalUserDisableFeatures 1250 } 1251 return &setting.Admin.UserDisabledFeatures 1252 }