users.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957
  1. // Copyright 2020 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package db
  5. import (
  6. "context"
  7. "fmt"
  8. "os"
  9. "strings"
  10. "time"
  11. "unicode/utf8"
  12. "github.com/go-macaron/binding"
  13. api "github.com/gogs/go-gogs-client"
  14. "github.com/pkg/errors"
  15. "gorm.io/gorm"
  16. log "unknwon.dev/clog/v2"
  17. "gogs.io/gogs/internal/auth"
  18. "gogs.io/gogs/internal/conf"
  19. "gogs.io/gogs/internal/cryptoutil"
  20. "gogs.io/gogs/internal/dbutil"
  21. "gogs.io/gogs/internal/errutil"
  22. "gogs.io/gogs/internal/osutil"
  23. "gogs.io/gogs/internal/repoutil"
  24. "gogs.io/gogs/internal/strutil"
  25. "gogs.io/gogs/internal/tool"
  26. "gogs.io/gogs/internal/userutil"
  27. )
  28. // UsersStore is the persistent interface for users.
  29. //
  30. // NOTE: All methods are sorted in alphabetical order.
  31. type UsersStore interface {
  32. // Authenticate validates username and password via given login source ID. It
  33. // returns ErrUserNotExist when the user was not found.
  34. //
  35. // When the "loginSourceID" is negative, it aborts the process and returns
  36. // ErrUserNotExist if the user was not found in the database.
  37. //
  38. // When the "loginSourceID" is non-negative, it returns ErrLoginSourceMismatch
  39. // if the user has different login source ID than the "loginSourceID".
  40. //
  41. // When the "loginSourceID" is positive, it tries to authenticate via given
  42. // login source and creates a new user when not yet exists in the database.
  43. Authenticate(ctx context.Context, username, password string, loginSourceID int64) (*User, error)
  44. // ChangeUsername changes the username of the given user and updates all
  45. // references to the old username. It returns ErrNameNotAllowed if the given
  46. // name or pattern of the name is not allowed as a username, or
  47. // ErrUserAlreadyExist when another user with same name already exists.
  48. ChangeUsername(ctx context.Context, userID int64, newUsername string) error
  49. // Count returns the total number of users.
  50. Count(ctx context.Context) int64
  51. // Create creates a new user and persists to database. It returns
  52. // ErrNameNotAllowed if the given name or pattern of the name is not allowed as
  53. // a username, or ErrUserAlreadyExist when a user with same name already exists,
  54. // or ErrEmailAlreadyUsed if the email has been used by another user.
  55. Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error)
  56. // DeleteCustomAvatar deletes the current user custom avatar and falls back to
  57. // use look up avatar by email.
  58. DeleteCustomAvatar(ctx context.Context, userID int64) error
  59. // GetByEmail returns the user (not organization) with given email. It ignores
  60. // records with unverified emails and returns ErrUserNotExist when not found.
  61. GetByEmail(ctx context.Context, email string) (*User, error)
  62. // GetByID returns the user with given ID. It returns ErrUserNotExist when not
  63. // found.
  64. GetByID(ctx context.Context, id int64) (*User, error)
  65. // GetByUsername returns the user with given username. It returns
  66. // ErrUserNotExist when not found.
  67. GetByUsername(ctx context.Context, username string) (*User, error)
  68. // HasForkedRepository returns true if the user has forked given repository.
  69. HasForkedRepository(ctx context.Context, userID, repoID int64) bool
  70. // IsUsernameUsed returns true if the given username has been used other than
  71. // the excluded user (a non-positive ID effectively meaning check against all
  72. // users).
  73. IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool
  74. // List returns a list of users. Results are paginated by given page and page
  75. // size, and sorted by primary key (id) in ascending order.
  76. List(ctx context.Context, page, pageSize int) ([]*User, error)
  77. // ListFollowers returns a list of users that are following the given user.
  78. // Results are paginated by given page and page size, and sorted by the time of
  79. // follow in descending order.
  80. ListFollowers(ctx context.Context, userID int64, page, pageSize int) ([]*User, error)
  81. // ListFollowings returns a list of users that are followed by the given user.
  82. // Results are paginated by given page and page size, and sorted by the time of
  83. // follow in descending order.
  84. ListFollowings(ctx context.Context, userID int64, page, pageSize int) ([]*User, error)
  85. // Update updates all fields for the given user, all values are persisted as-is
  86. // (i.e. empty values would overwrite/wipe out existing values).
  87. Update(ctx context.Context, userID int64, opts UpdateUserOptions) error
  88. // UseCustomAvatar uses the given avatar as the user custom avatar.
  89. UseCustomAvatar(ctx context.Context, userID int64, avatar []byte) error
  90. }
  91. var Users UsersStore
  92. var _ UsersStore = (*users)(nil)
  93. type users struct {
  94. *gorm.DB
  95. }
  96. // NewUsersStore returns a persistent interface for users with given database
  97. // connection.
  98. func NewUsersStore(db *gorm.DB) UsersStore {
  99. return &users{DB: db}
  100. }
  101. type ErrLoginSourceMismatch struct {
  102. args errutil.Args
  103. }
  104. // IsErrLoginSourceMismatch returns true if the underlying error has the type
  105. // ErrLoginSourceMismatch.
  106. func IsErrLoginSourceMismatch(err error) bool {
  107. _, ok := errors.Cause(err).(ErrLoginSourceMismatch)
  108. return ok
  109. }
  110. func (err ErrLoginSourceMismatch) Error() string {
  111. return fmt.Sprintf("login source mismatch: %v", err.args)
  112. }
  113. func (db *users) Authenticate(ctx context.Context, login, password string, loginSourceID int64) (*User, error) {
  114. login = strings.ToLower(login)
  115. query := db.WithContext(ctx)
  116. if strings.Contains(login, "@") {
  117. query = query.Where("email = ?", login)
  118. } else {
  119. query = query.Where("lower_name = ?", login)
  120. }
  121. user := new(User)
  122. err := query.First(user).Error
  123. if err != nil && err != gorm.ErrRecordNotFound {
  124. return nil, errors.Wrap(err, "get user")
  125. }
  126. var authSourceID int64 // The login source ID will be used to authenticate the user
  127. createNewUser := false // Whether to create a new user after successful authentication
  128. // User found in the database
  129. if err == nil {
  130. // Note: This check is unnecessary but to reduce user confusion at login page
  131. // and make it more consistent from user's perspective.
  132. if loginSourceID >= 0 && user.LoginSource != loginSourceID {
  133. return nil, ErrLoginSourceMismatch{args: errutil.Args{"expect": loginSourceID, "actual": user.LoginSource}}
  134. }
  135. // Validate password hash fetched from database for local accounts.
  136. if user.IsLocal() {
  137. if userutil.ValidatePassword(user.Password, user.Salt, password) {
  138. return user, nil
  139. }
  140. return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login, "userID": user.ID}}
  141. }
  142. authSourceID = user.LoginSource
  143. } else {
  144. // Non-local login source is always greater than 0.
  145. if loginSourceID <= 0 {
  146. return nil, auth.ErrBadCredentials{Args: map[string]interface{}{"login": login}}
  147. }
  148. authSourceID = loginSourceID
  149. createNewUser = true
  150. }
  151. source, err := LoginSources.GetByID(ctx, authSourceID)
  152. if err != nil {
  153. return nil, errors.Wrap(err, "get login source")
  154. }
  155. if !source.IsActived {
  156. return nil, errors.Errorf("login source %d is not activated", source.ID)
  157. }
  158. extAccount, err := source.Provider.Authenticate(login, password)
  159. if err != nil {
  160. return nil, err
  161. }
  162. if !createNewUser {
  163. return user, nil
  164. }
  165. // Validate username make sure it satisfies requirement.
  166. if binding.AlphaDashDotPattern.MatchString(extAccount.Name) {
  167. return nil, fmt.Errorf("invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", extAccount.Name)
  168. }
  169. return db.Create(ctx, extAccount.Name, extAccount.Email,
  170. CreateUserOptions{
  171. FullName: extAccount.FullName,
  172. LoginSource: authSourceID,
  173. LoginName: extAccount.Login,
  174. Location: extAccount.Location,
  175. Website: extAccount.Website,
  176. Activated: true,
  177. Admin: extAccount.Admin,
  178. },
  179. )
  180. }
  181. func (db *users) ChangeUsername(ctx context.Context, userID int64, newUsername string) error {
  182. err := isUsernameAllowed(newUsername)
  183. if err != nil {
  184. return err
  185. }
  186. if db.IsUsernameUsed(ctx, newUsername, userID) {
  187. return ErrUserAlreadyExist{
  188. args: errutil.Args{
  189. "name": newUsername,
  190. },
  191. }
  192. }
  193. user, err := db.GetByID(ctx, userID)
  194. if err != nil {
  195. return errors.Wrap(err, "get user")
  196. }
  197. return db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  198. err := tx.Model(&User{}).
  199. Where("id = ?", user.ID).
  200. Updates(map[string]any{
  201. "lower_name": strings.ToLower(newUsername),
  202. "name": newUsername,
  203. "updated_unix": tx.NowFunc().Unix(),
  204. }).Error
  205. if err != nil {
  206. return errors.Wrap(err, "update user name")
  207. }
  208. // Stop here if it's just a case-change of the username
  209. if strings.EqualFold(user.Name, newUsername) {
  210. return nil
  211. }
  212. // Update all references to the user name in pull requests
  213. err = tx.Model(&PullRequest{}).
  214. Where("head_user_name = ?", user.LowerName).
  215. Update("head_user_name", strings.ToLower(newUsername)).
  216. Error
  217. if err != nil {
  218. return errors.Wrap(err, `update "pull_request.head_user_name"`)
  219. }
  220. // Delete local copies of repositories and their wikis that are owned by the user
  221. rows, err := tx.Model(&Repository{}).Where("owner_id = ?", user.ID).Rows()
  222. if err != nil {
  223. return errors.Wrap(err, "iterate repositories")
  224. }
  225. defer func() { _ = rows.Close() }()
  226. for rows.Next() {
  227. var repo struct {
  228. ID int64
  229. }
  230. err = tx.ScanRows(rows, &repo)
  231. if err != nil {
  232. return errors.Wrap(err, "scan rows")
  233. }
  234. deleteRepoLocalCopy(repo.ID)
  235. RemoveAllWithNotice(fmt.Sprintf("Delete repository %d wiki local copy", repo.ID), repoutil.RepositoryLocalWikiPath(repo.ID))
  236. }
  237. if err = rows.Err(); err != nil {
  238. return errors.Wrap(err, "check rows.Err")
  239. }
  240. // Rename user directory if exists
  241. userPath := repoutil.UserPath(user.Name)
  242. if osutil.IsExist(userPath) {
  243. newUserPath := repoutil.UserPath(newUsername)
  244. err = os.Rename(userPath, newUserPath)
  245. if err != nil {
  246. return errors.Wrap(err, "rename user directory")
  247. }
  248. }
  249. return nil
  250. })
  251. }
  252. func (db *users) Count(ctx context.Context) int64 {
  253. var count int64
  254. db.WithContext(ctx).Model(&User{}).Where("type = ?", UserTypeIndividual).Count(&count)
  255. return count
  256. }
  257. type CreateUserOptions struct {
  258. FullName string
  259. Password string
  260. LoginSource int64
  261. LoginName string
  262. Location string
  263. Website string
  264. Activated bool
  265. Admin bool
  266. }
  267. type ErrUserAlreadyExist struct {
  268. args errutil.Args
  269. }
  270. // IsErrUserAlreadyExist returns true if the underlying error has the type
  271. // ErrUserAlreadyExist.
  272. func IsErrUserAlreadyExist(err error) bool {
  273. _, ok := errors.Cause(err).(ErrUserAlreadyExist)
  274. return ok
  275. }
  276. func (err ErrUserAlreadyExist) Error() string {
  277. return fmt.Sprintf("user already exists: %v", err.args)
  278. }
  279. type ErrEmailAlreadyUsed struct {
  280. args errutil.Args
  281. }
  282. func IsErrEmailAlreadyUsed(err error) bool {
  283. _, ok := err.(ErrEmailAlreadyUsed)
  284. return ok
  285. }
  286. func (err ErrEmailAlreadyUsed) Email() string {
  287. email, ok := err.args["email"].(string)
  288. if ok {
  289. return email
  290. }
  291. return "<email not found>"
  292. }
  293. func (err ErrEmailAlreadyUsed) Error() string {
  294. return fmt.Sprintf("email has been used: %v", err.args)
  295. }
  296. func (db *users) Create(ctx context.Context, username, email string, opts CreateUserOptions) (*User, error) {
  297. err := isUsernameAllowed(username)
  298. if err != nil {
  299. return nil, err
  300. }
  301. if db.IsUsernameUsed(ctx, username, 0) {
  302. return nil, ErrUserAlreadyExist{
  303. args: errutil.Args{
  304. "name": username,
  305. },
  306. }
  307. }
  308. email = strings.ToLower(email)
  309. _, err = db.GetByEmail(ctx, email)
  310. if err == nil {
  311. return nil, ErrEmailAlreadyUsed{
  312. args: errutil.Args{
  313. "email": email,
  314. },
  315. }
  316. } else if !IsErrUserNotExist(err) {
  317. return nil, err
  318. }
  319. user := &User{
  320. LowerName: strings.ToLower(username),
  321. Name: username,
  322. FullName: opts.FullName,
  323. Email: email,
  324. Password: opts.Password,
  325. LoginSource: opts.LoginSource,
  326. LoginName: opts.LoginName,
  327. Location: opts.Location,
  328. Website: opts.Website,
  329. MaxRepoCreation: -1,
  330. IsActive: opts.Activated,
  331. IsAdmin: opts.Admin,
  332. Avatar: cryptoutil.MD5(email), // Gravatar URL uses the MD5 hash of the email, see https://en.gravatar.com/site/implement/hash/
  333. AvatarEmail: email,
  334. }
  335. user.Rands, err = userutil.RandomSalt()
  336. if err != nil {
  337. return nil, err
  338. }
  339. user.Salt, err = userutil.RandomSalt()
  340. if err != nil {
  341. return nil, err
  342. }
  343. user.Password = userutil.EncodePassword(user.Password, user.Salt)
  344. return user, db.WithContext(ctx).Create(user).Error
  345. }
  346. func (db *users) DeleteCustomAvatar(ctx context.Context, userID int64) error {
  347. _ = os.Remove(userutil.CustomAvatarPath(userID))
  348. return db.WithContext(ctx).
  349. Model(&User{}).
  350. Where("id = ?", userID).
  351. Updates(map[string]interface{}{
  352. "use_custom_avatar": false,
  353. "updated_unix": db.NowFunc().Unix(),
  354. }).
  355. Error
  356. }
  357. var _ errutil.NotFound = (*ErrUserNotExist)(nil)
  358. type ErrUserNotExist struct {
  359. args errutil.Args
  360. }
  361. func IsErrUserNotExist(err error) bool {
  362. _, ok := err.(ErrUserNotExist)
  363. return ok
  364. }
  365. func (err ErrUserNotExist) Error() string {
  366. return fmt.Sprintf("user does not exist: %v", err.args)
  367. }
  368. func (ErrUserNotExist) NotFound() bool {
  369. return true
  370. }
  371. func (db *users) GetByEmail(ctx context.Context, email string) (*User, error) {
  372. if email == "" {
  373. return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
  374. }
  375. email = strings.ToLower(email)
  376. // First try to find the user by primary email
  377. user := new(User)
  378. err := db.WithContext(ctx).
  379. Where("email = ? AND type = ? AND is_active = ?", email, UserTypeIndividual, true).
  380. First(user).
  381. Error
  382. if err == nil {
  383. return user, nil
  384. } else if err != gorm.ErrRecordNotFound {
  385. return nil, err
  386. }
  387. // Otherwise, check activated email addresses
  388. emailAddress, err := NewEmailAddressesStore(db.DB).GetByEmail(ctx, email, true)
  389. if err != nil {
  390. if IsErrEmailAddressNotExist(err) {
  391. return nil, ErrUserNotExist{args: errutil.Args{"email": email}}
  392. }
  393. return nil, err
  394. }
  395. return db.GetByID(ctx, emailAddress.UserID)
  396. }
  397. func (db *users) GetByID(ctx context.Context, id int64) (*User, error) {
  398. user := new(User)
  399. err := db.WithContext(ctx).Where("id = ?", id).First(user).Error
  400. if err != nil {
  401. if err == gorm.ErrRecordNotFound {
  402. return nil, ErrUserNotExist{args: errutil.Args{"userID": id}}
  403. }
  404. return nil, err
  405. }
  406. return user, nil
  407. }
  408. func (db *users) GetByUsername(ctx context.Context, username string) (*User, error) {
  409. user := new(User)
  410. err := db.WithContext(ctx).Where("lower_name = ?", strings.ToLower(username)).First(user).Error
  411. if err != nil {
  412. if err == gorm.ErrRecordNotFound {
  413. return nil, ErrUserNotExist{args: errutil.Args{"name": username}}
  414. }
  415. return nil, err
  416. }
  417. return user, nil
  418. }
  419. func (db *users) HasForkedRepository(ctx context.Context, userID, repoID int64) bool {
  420. var count int64
  421. db.WithContext(ctx).Model(new(Repository)).Where("owner_id = ? AND fork_id = ?", userID, repoID).Count(&count)
  422. return count > 0
  423. }
  424. func (db *users) IsUsernameUsed(ctx context.Context, username string, excludeUserId int64) bool {
  425. if username == "" {
  426. return false
  427. }
  428. return db.WithContext(ctx).
  429. Select("id").
  430. Where("lower_name = ? AND id != ?", strings.ToLower(username), excludeUserId).
  431. First(&User{}).
  432. Error != gorm.ErrRecordNotFound
  433. }
  434. func (db *users) List(ctx context.Context, page, pageSize int) ([]*User, error) {
  435. users := make([]*User, 0, pageSize)
  436. return users, db.WithContext(ctx).
  437. Where("type = ?", UserTypeIndividual).
  438. Limit(pageSize).Offset((page - 1) * pageSize).
  439. Order("id ASC").
  440. Find(&users).
  441. Error
  442. }
  443. func (db *users) ListFollowers(ctx context.Context, userID int64, page, pageSize int) ([]*User, error) {
  444. /*
  445. Equivalent SQL for PostgreSQL:
  446. SELECT * FROM "user"
  447. LEFT JOIN follow ON follow.user_id = "user".id
  448. WHERE follow.follow_id = @userID
  449. ORDER BY follow.id DESC
  450. LIMIT @limit OFFSET @offset
  451. */
  452. users := make([]*User, 0, pageSize)
  453. return users, db.WithContext(ctx).
  454. Joins(dbutil.Quote("LEFT JOIN follow ON follow.user_id = %s.id", "user")).
  455. Where("follow.follow_id = ?", userID).
  456. Limit(pageSize).Offset((page - 1) * pageSize).
  457. Order("follow.id DESC").
  458. Find(&users).
  459. Error
  460. }
  461. func (db *users) ListFollowings(ctx context.Context, userID int64, page, pageSize int) ([]*User, error) {
  462. /*
  463. Equivalent SQL for PostgreSQL:
  464. SELECT * FROM "user"
  465. LEFT JOIN follow ON follow.user_id = "user".id
  466. WHERE follow.user_id = @userID
  467. ORDER BY follow.id DESC
  468. LIMIT @limit OFFSET @offset
  469. */
  470. users := make([]*User, 0, pageSize)
  471. return users, db.WithContext(ctx).
  472. Joins(dbutil.Quote("LEFT JOIN follow ON follow.follow_id = %s.id", "user")).
  473. Where("follow.user_id = ?", userID).
  474. Limit(pageSize).Offset((page - 1) * pageSize).
  475. Order("follow.id DESC").
  476. Find(&users).
  477. Error
  478. }
  479. type UpdateUserOptions struct {
  480. FullName string
  481. Website string
  482. Location string
  483. Description string
  484. MaxRepoCreation int
  485. }
  486. func (db *users) Update(ctx context.Context, userID int64, opts UpdateUserOptions) error {
  487. if opts.MaxRepoCreation < -1 {
  488. opts.MaxRepoCreation = -1
  489. }
  490. return db.WithContext(ctx).
  491. Model(&User{}).
  492. Where("id = ?", userID).
  493. Updates(map[string]any{
  494. "full_name": strutil.Truncate(opts.FullName, 255),
  495. "website": strutil.Truncate(opts.Website, 255),
  496. "location": strutil.Truncate(opts.Location, 255),
  497. "description": strutil.Truncate(opts.Description, 255),
  498. "max_repo_creation": opts.MaxRepoCreation,
  499. "updated_unix": db.NowFunc().Unix(),
  500. }).
  501. Error
  502. }
  503. func (db *users) UseCustomAvatar(ctx context.Context, userID int64, avatar []byte) error {
  504. err := userutil.SaveAvatar(userID, avatar)
  505. if err != nil {
  506. return errors.Wrap(err, "save avatar")
  507. }
  508. return db.WithContext(ctx).
  509. Model(&User{}).
  510. Where("id = ?", userID).
  511. Updates(map[string]interface{}{
  512. "use_custom_avatar": true,
  513. "updated_unix": db.NowFunc().Unix(),
  514. }).
  515. Error
  516. }
  517. // UserType indicates the type of the user account.
  518. type UserType int
  519. const (
  520. UserTypeIndividual UserType = iota // NOTE: Historic reason to make it starts at 0.
  521. UserTypeOrganization
  522. )
  523. // User represents the object of an individual or an organization.
  524. type User struct {
  525. ID int64 `gorm:"primaryKey"`
  526. LowerName string `xorm:"UNIQUE NOT NULL" gorm:"unique;not null"`
  527. Name string `xorm:"UNIQUE NOT NULL" gorm:"not null"`
  528. FullName string
  529. // Email is the primary email address (to be used for communication)
  530. Email string `xorm:"NOT NULL" gorm:"not null"`
  531. Password string `xorm:"passwd NOT NULL" gorm:"column:passwd;not null"`
  532. LoginSource int64 `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
  533. LoginName string
  534. Type UserType
  535. Location string
  536. Website string
  537. Rands string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
  538. Salt string `xorm:"VARCHAR(10)" gorm:"type:VARCHAR(10)"`
  539. Created time.Time `xorm:"-" gorm:"-" json:"-"`
  540. CreatedUnix int64
  541. Updated time.Time `xorm:"-" gorm:"-" json:"-"`
  542. UpdatedUnix int64
  543. // Remember visibility choice for convenience, true for private
  544. LastRepoVisibility bool
  545. // Maximum repository creation limit, -1 means use global default
  546. MaxRepoCreation int `xorm:"NOT NULL DEFAULT -1" gorm:"not null;default:-1"`
  547. // Permissions
  548. IsActive bool // Activate primary email
  549. IsAdmin bool
  550. AllowGitHook bool
  551. AllowImportLocal bool // Allow migrate repository by local path
  552. ProhibitLogin bool
  553. // Avatar
  554. Avatar string `xorm:"VARCHAR(2048) NOT NULL" gorm:"type:VARCHAR(2048);not null"`
  555. AvatarEmail string `xorm:"NOT NULL" gorm:"not null"`
  556. UseCustomAvatar bool
  557. // Counters
  558. NumFollowers int
  559. NumFollowing int `xorm:"NOT NULL DEFAULT 0" gorm:"not null;default:0"`
  560. NumStars int
  561. NumRepos int
  562. // For organization
  563. Description string
  564. NumTeams int
  565. NumMembers int
  566. Teams []*Team `xorm:"-" gorm:"-" json:"-"`
  567. Members []*User `xorm:"-" gorm:"-" json:"-"`
  568. }
  569. // BeforeCreate implements the GORM create hook.
  570. func (u *User) BeforeCreate(tx *gorm.DB) error {
  571. if u.CreatedUnix == 0 {
  572. u.CreatedUnix = tx.NowFunc().Unix()
  573. u.UpdatedUnix = u.CreatedUnix
  574. }
  575. return nil
  576. }
  577. // AfterFind implements the GORM query hook.
  578. func (u *User) AfterFind(_ *gorm.DB) error {
  579. u.Created = time.Unix(u.CreatedUnix, 0).Local()
  580. u.Updated = time.Unix(u.UpdatedUnix, 0).Local()
  581. return nil
  582. }
  583. // IsLocal returns true if the user is created as local account.
  584. func (u *User) IsLocal() bool {
  585. return u.LoginSource <= 0
  586. }
  587. // IsOrganization returns true if the user is an organization.
  588. func (u *User) IsOrganization() bool {
  589. return u.Type == UserTypeOrganization
  590. }
  591. // IsMailable returns true if the user is eligible to receive emails.
  592. func (u *User) IsMailable() bool {
  593. return u.IsActive
  594. }
  595. // APIFormat returns the API format of a user.
  596. func (u *User) APIFormat() *api.User {
  597. return &api.User{
  598. ID: u.ID,
  599. UserName: u.Name,
  600. Login: u.Name,
  601. FullName: u.FullName,
  602. Email: u.Email,
  603. AvatarUrl: u.AvatarURL(),
  604. }
  605. }
  606. // maxNumRepos returns the maximum number of repositories that the user can have
  607. // direct ownership.
  608. func (u *User) maxNumRepos() int {
  609. if u.MaxRepoCreation <= -1 {
  610. return conf.Repository.MaxCreationLimit
  611. }
  612. return u.MaxRepoCreation
  613. }
  614. // canCreateRepo returns true if the user can create a repository.
  615. func (u *User) canCreateRepo() bool {
  616. return u.maxNumRepos() <= -1 || u.NumRepos < u.maxNumRepos()
  617. }
  618. // CanCreateOrganization returns true if user can create organizations.
  619. func (u *User) CanCreateOrganization() bool {
  620. return !conf.Admin.DisableRegularOrgCreation || u.IsAdmin
  621. }
  622. // CanEditGitHook returns true if user can edit Git hooks.
  623. func (u *User) CanEditGitHook() bool {
  624. return u.IsAdmin || u.AllowGitHook
  625. }
  626. // CanImportLocal returns true if user can migrate repositories by local path.
  627. func (u *User) CanImportLocal() bool {
  628. return conf.Repository.EnableLocalPathMigration && (u.IsAdmin || u.AllowImportLocal)
  629. }
  630. // DisplayName returns the full name of the user if it's not empty, returns the
  631. // username otherwise.
  632. func (u *User) DisplayName() string {
  633. if len(u.FullName) > 0 {
  634. return u.FullName
  635. }
  636. return u.Name
  637. }
  638. // HomeURLPath returns the URL path to the user or organization home page.
  639. //
  640. // TODO(unknwon): This is also used in templates, which should be fixed by
  641. // having a dedicated type `template.User` and move this to the "userutil"
  642. // package.
  643. func (u *User) HomeURLPath() string {
  644. return conf.Server.Subpath + "/" + u.Name
  645. }
  646. // HTMLURL returns the full URL to the user or organization home page.
  647. //
  648. // TODO(unknwon): This is also used in templates, which should be fixed by
  649. // having a dedicated type `template.User` and move this to the "userutil"
  650. // package.
  651. func (u *User) HTMLURL() string {
  652. return conf.Server.ExternalURL + u.Name
  653. }
  654. // AvatarURLPath returns the URL path to the user or organization avatar. If the
  655. // user enables Gravatar-like service, then an external URL will be returned.
  656. //
  657. // TODO(unknwon): This is also used in templates, which should be fixed by
  658. // having a dedicated type `template.User` and move this to the "userutil"
  659. // package.
  660. func (u *User) AvatarURLPath() string {
  661. defaultURLPath := conf.UserDefaultAvatarURLPath()
  662. if u.ID <= 0 {
  663. return defaultURLPath
  664. }
  665. hasCustomAvatar := osutil.IsFile(userutil.CustomAvatarPath(u.ID))
  666. switch {
  667. case u.UseCustomAvatar:
  668. if !hasCustomAvatar {
  669. return defaultURLPath
  670. }
  671. return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarPathPrefix, u.ID)
  672. case conf.Picture.DisableGravatar:
  673. if !hasCustomAvatar {
  674. if err := userutil.GenerateRandomAvatar(u.ID, u.Name, u.Email); err != nil {
  675. log.Error("Failed to generate random avatar [user_id: %d]: %v", u.ID, err)
  676. }
  677. }
  678. return fmt.Sprintf("%s/%s/%d", conf.Server.Subpath, conf.UsersAvatarPathPrefix, u.ID)
  679. }
  680. return tool.AvatarLink(u.AvatarEmail)
  681. }
  682. // AvatarURL returns the full URL to the user or organization avatar. If the
  683. // user enables Gravatar-like service, then an external URL will be returned.
  684. //
  685. // TODO(unknwon): This is also used in templates, which should be fixed by
  686. // having a dedicated type `template.User` and move this to the "userutil"
  687. // package.
  688. func (u *User) AvatarURL() string {
  689. link := u.AvatarURLPath()
  690. if link[0] == '/' && link[1] != '/' {
  691. return conf.Server.ExternalURL + strings.TrimPrefix(link, conf.Server.Subpath)[1:]
  692. }
  693. return link
  694. }
  695. // IsFollowing returns true if the user is following the given user.
  696. //
  697. // TODO(unknwon): This is also used in templates, which should be fixed by
  698. // having a dedicated type `template.User`.
  699. func (u *User) IsFollowing(followID int64) bool {
  700. return Follows.IsFollowing(context.TODO(), u.ID, followID)
  701. }
  702. // IsUserOrgOwner returns true if the user is in the owner team of the given
  703. // organization.
  704. //
  705. // TODO(unknwon): This is also used in templates, which should be fixed by
  706. // having a dedicated type `template.User`.
  707. func (u *User) IsUserOrgOwner(orgId int64) bool {
  708. return IsOrganizationOwner(orgId, u.ID)
  709. }
  710. // IsPublicMember returns true if the user has public membership of the given
  711. // organization.
  712. //
  713. // TODO(unknwon): This is also used in templates, which should be fixed by
  714. // having a dedicated type `template.User`.
  715. func (u *User) IsPublicMember(orgId int64) bool {
  716. return IsPublicMembership(orgId, u.ID)
  717. }
  718. // GetOrganizationCount returns the count of organization membership that the
  719. // user has.
  720. //
  721. // TODO(unknwon): This is also used in templates, which should be fixed by
  722. // having a dedicated type `template.User`.
  723. func (u *User) GetOrganizationCount() (int64, error) {
  724. return OrgUsers.CountByUser(context.TODO(), u.ID)
  725. }
  726. // ShortName truncates and returns the username at most in given length.
  727. //
  728. // TODO(unknwon): This is also used in templates, which should be fixed by
  729. // having a dedicated type `template.User`.
  730. func (u *User) ShortName(length int) string {
  731. return strutil.Ellipsis(u.Name, length)
  732. }
  733. // NewGhostUser creates and returns a fake user for people who has deleted their
  734. // accounts.
  735. //
  736. // TODO: Once migrated to unknwon.dev/i18n, pass in the `i18n.Locale` to
  737. // translate the text to local language.
  738. func NewGhostUser() *User {
  739. return &User{
  740. ID: -1,
  741. Name: "Ghost",
  742. LowerName: "ghost",
  743. }
  744. }
  745. var (
  746. reservedUsernames = map[string]struct{}{
  747. "-": {},
  748. "explore": {},
  749. "create": {},
  750. "assets": {},
  751. "css": {},
  752. "img": {},
  753. "js": {},
  754. "less": {},
  755. "plugins": {},
  756. "debug": {},
  757. "raw": {},
  758. "install": {},
  759. "api": {},
  760. "avatar": {},
  761. "user": {},
  762. "org": {},
  763. "help": {},
  764. "stars": {},
  765. "issues": {},
  766. "pulls": {},
  767. "commits": {},
  768. "repo": {},
  769. "template": {},
  770. "admin": {},
  771. "new": {},
  772. ".": {},
  773. "..": {},
  774. }
  775. reservedUsernamePatterns = []string{"*.keys"}
  776. )
  777. type ErrNameNotAllowed struct {
  778. args errutil.Args
  779. }
  780. // IsErrNameNotAllowed returns true if the underlying error has the type
  781. // ErrNameNotAllowed.
  782. func IsErrNameNotAllowed(err error) bool {
  783. _, ok := errors.Cause(err).(ErrNameNotAllowed)
  784. return ok
  785. }
  786. func (err ErrNameNotAllowed) Value() string {
  787. val, ok := err.args["name"].(string)
  788. if ok {
  789. return val
  790. }
  791. val, ok = err.args["pattern"].(string)
  792. if ok {
  793. return val
  794. }
  795. return "<value not found>"
  796. }
  797. func (err ErrNameNotAllowed) Error() string {
  798. return fmt.Sprintf("name is not allowed: %v", err.args)
  799. }
  800. // isNameAllowed checks if the name is reserved or pattern of the name is not
  801. // allowed based on given reserved names and patterns. Names are exact match,
  802. // patterns can be prefix or suffix match with the wildcard ("*").
  803. func isNameAllowed(names map[string]struct{}, patterns []string, name string) error {
  804. name = strings.TrimSpace(strings.ToLower(name))
  805. if utf8.RuneCountInString(name) == 0 {
  806. return ErrNameNotAllowed{
  807. args: errutil.Args{
  808. "reason": "empty name",
  809. },
  810. }
  811. }
  812. if _, ok := names[name]; ok {
  813. return ErrNameNotAllowed{
  814. args: errutil.Args{
  815. "reason": "reserved",
  816. "name": name,
  817. },
  818. }
  819. }
  820. for _, pattern := range patterns {
  821. if pattern[0] == '*' && strings.HasSuffix(name, pattern[1:]) ||
  822. (pattern[len(pattern)-1] == '*' && strings.HasPrefix(name, pattern[:len(pattern)-1])) {
  823. return ErrNameNotAllowed{
  824. args: errutil.Args{
  825. "reason": "reserved",
  826. "pattern": pattern,
  827. },
  828. }
  829. }
  830. }
  831. return nil
  832. }
  833. // isUsernameAllowed returns ErrNameNotAllowed if the given name or pattern of
  834. // the name is not allowed as a username.
  835. func isUsernameAllowed(name string) error {
  836. return isNameAllowed(reservedUsernames, reservedUsernamePatterns, name)
  837. }