You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

org_team.go 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // Copyright 2016 The Gogs 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. package models
  6. import (
  7. "context"
  8. "errors"
  9. "fmt"
  10. "strings"
  11. "code.gitea.io/gitea/models/db"
  12. git_model "code.gitea.io/gitea/models/git"
  13. issues_model "code.gitea.io/gitea/models/issues"
  14. "code.gitea.io/gitea/models/organization"
  15. access_model "code.gitea.io/gitea/models/perm/access"
  16. repo_model "code.gitea.io/gitea/models/repo"
  17. user_model "code.gitea.io/gitea/models/user"
  18. "code.gitea.io/gitea/modules/log"
  19. "code.gitea.io/gitea/modules/setting"
  20. "code.gitea.io/gitea/modules/util"
  21. "xorm.io/builder"
  22. )
  23. func addRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository) (err error) {
  24. if err = organization.AddTeamRepo(ctx, t.OrgID, t.ID, repo.ID); err != nil {
  25. return err
  26. }
  27. if _, err = db.GetEngine(ctx).Incr("num_repos").ID(t.ID).Update(new(organization.Team)); err != nil {
  28. return fmt.Errorf("update team: %v", err)
  29. }
  30. t.NumRepos++
  31. if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
  32. return fmt.Errorf("recalculateAccesses: %v", err)
  33. }
  34. // Make all team members watch this repo if enabled in global settings
  35. if setting.Service.AutoWatchNewRepos {
  36. if err = t.GetMembersCtx(ctx); err != nil {
  37. return fmt.Errorf("getMembers: %v", err)
  38. }
  39. for _, u := range t.Members {
  40. if err = repo_model.WatchRepo(ctx, u.ID, repo.ID, true); err != nil {
  41. return fmt.Errorf("watchRepo: %v", err)
  42. }
  43. }
  44. }
  45. return nil
  46. }
  47. // addAllRepositories adds all repositories to the team.
  48. // If the team already has some repositories they will be left unchanged.
  49. func addAllRepositories(ctx context.Context, t *organization.Team) error {
  50. var orgRepos []repo_model.Repository
  51. e := db.GetEngine(ctx)
  52. if err := e.Where("owner_id = ?", t.OrgID).Find(&orgRepos); err != nil {
  53. return fmt.Errorf("get org repos: %v", err)
  54. }
  55. for _, repo := range orgRepos {
  56. if !organization.HasTeamRepo(ctx, t.OrgID, t.ID, repo.ID) {
  57. if err := addRepository(ctx, t, &repo); err != nil {
  58. return fmt.Errorf("addRepository: %v", err)
  59. }
  60. }
  61. }
  62. return nil
  63. }
  64. // AddAllRepositories adds all repositories to the team
  65. func AddAllRepositories(t *organization.Team) (err error) {
  66. ctx, committer, err := db.TxContext()
  67. if err != nil {
  68. return err
  69. }
  70. defer committer.Close()
  71. if err = addAllRepositories(ctx, t); err != nil {
  72. return err
  73. }
  74. return committer.Commit()
  75. }
  76. // AddRepository adds new repository to team of organization.
  77. func AddRepository(t *organization.Team, repo *repo_model.Repository) (err error) {
  78. if repo.OwnerID != t.OrgID {
  79. return errors.New("Repository does not belong to organization")
  80. } else if HasRepository(t, repo.ID) {
  81. return nil
  82. }
  83. ctx, committer, err := db.TxContext()
  84. if err != nil {
  85. return err
  86. }
  87. defer committer.Close()
  88. if err = addRepository(ctx, t, repo); err != nil {
  89. return err
  90. }
  91. return committer.Commit()
  92. }
  93. // RemoveAllRepositories removes all repositories from team and recalculates access
  94. func RemoveAllRepositories(t *organization.Team) (err error) {
  95. if t.IncludesAllRepositories {
  96. return nil
  97. }
  98. ctx, committer, err := db.TxContext()
  99. if err != nil {
  100. return err
  101. }
  102. defer committer.Close()
  103. if err = removeAllRepositories(ctx, t); err != nil {
  104. return err
  105. }
  106. return committer.Commit()
  107. }
  108. // removeAllRepositories removes all repositories from team and recalculates access
  109. // Note: Shall not be called if team includes all repositories
  110. func removeAllRepositories(ctx context.Context, t *organization.Team) (err error) {
  111. e := db.GetEngine(ctx)
  112. // Delete all accesses.
  113. for _, repo := range t.Repos {
  114. if err := access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
  115. return err
  116. }
  117. // Remove watches from all users and now unaccessible repos
  118. for _, user := range t.Members {
  119. has, err := access_model.HasAccess(ctx, user.ID, repo)
  120. if err != nil {
  121. return err
  122. } else if has {
  123. continue
  124. }
  125. if err = repo_model.WatchRepo(ctx, user.ID, repo.ID, false); err != nil {
  126. return err
  127. }
  128. // Remove all IssueWatches a user has subscribed to in the repositories
  129. if err = issues_model.RemoveIssueWatchersByRepoID(ctx, user.ID, repo.ID); err != nil {
  130. return err
  131. }
  132. }
  133. }
  134. // Delete team-repo
  135. if _, err := e.
  136. Where("team_id=?", t.ID).
  137. Delete(new(organization.TeamRepo)); err != nil {
  138. return err
  139. }
  140. t.NumRepos = 0
  141. if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
  142. return err
  143. }
  144. return nil
  145. }
  146. // HasRepository returns true if given repository belong to team.
  147. func HasRepository(t *organization.Team, repoID int64) bool {
  148. return organization.HasTeamRepo(db.DefaultContext, t.OrgID, t.ID, repoID)
  149. }
  150. // removeRepository removes a repository from a team and recalculates access
  151. // Note: Repository shall not be removed from team if it includes all repositories (unless the repository is deleted)
  152. func removeRepository(ctx context.Context, t *organization.Team, repo *repo_model.Repository, recalculate bool) (err error) {
  153. e := db.GetEngine(ctx)
  154. if err = organization.RemoveTeamRepo(ctx, t.ID, repo.ID); err != nil {
  155. return err
  156. }
  157. t.NumRepos--
  158. if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
  159. return err
  160. }
  161. // Don't need to recalculate when delete a repository from organization.
  162. if recalculate {
  163. if err = access_model.RecalculateTeamAccesses(ctx, repo, t.ID); err != nil {
  164. return err
  165. }
  166. }
  167. teamUsers, err := organization.GetTeamUsersByTeamID(ctx, t.ID)
  168. if err != nil {
  169. return fmt.Errorf("getTeamUsersByTeamID: %v", err)
  170. }
  171. for _, teamUser := range teamUsers {
  172. has, err := access_model.HasAccess(ctx, teamUser.UID, repo)
  173. if err != nil {
  174. return err
  175. } else if has {
  176. continue
  177. }
  178. if err = repo_model.WatchRepo(ctx, teamUser.UID, repo.ID, false); err != nil {
  179. return err
  180. }
  181. // Remove all IssueWatches a user has subscribed to in the repositories
  182. if err := issues_model.RemoveIssueWatchersByRepoID(ctx, teamUser.UID, repo.ID); err != nil {
  183. return err
  184. }
  185. }
  186. return nil
  187. }
  188. // RemoveRepository removes repository from team of organization.
  189. // If the team shall include all repositories the request is ignored.
  190. func RemoveRepository(t *organization.Team, repoID int64) error {
  191. if !HasRepository(t, repoID) {
  192. return nil
  193. }
  194. if t.IncludesAllRepositories {
  195. return nil
  196. }
  197. repo, err := repo_model.GetRepositoryByID(repoID)
  198. if err != nil {
  199. return err
  200. }
  201. ctx, committer, err := db.TxContext()
  202. if err != nil {
  203. return err
  204. }
  205. defer committer.Close()
  206. if err = removeRepository(ctx, t, repo, true); err != nil {
  207. return err
  208. }
  209. return committer.Commit()
  210. }
  211. // NewTeam creates a record of new team.
  212. // It's caller's responsibility to assign organization ID.
  213. func NewTeam(t *organization.Team) (err error) {
  214. if len(t.Name) == 0 {
  215. return errors.New("empty team name")
  216. }
  217. if err = organization.IsUsableTeamName(t.Name); err != nil {
  218. return err
  219. }
  220. has, err := db.GetEngine(db.DefaultContext).ID(t.OrgID).Get(new(user_model.User))
  221. if err != nil {
  222. return err
  223. }
  224. if !has {
  225. return organization.ErrOrgNotExist{ID: t.OrgID}
  226. }
  227. t.LowerName = strings.ToLower(t.Name)
  228. has, err = db.GetEngine(db.DefaultContext).
  229. Where("org_id=?", t.OrgID).
  230. And("lower_name=?", t.LowerName).
  231. Get(new(organization.Team))
  232. if err != nil {
  233. return err
  234. }
  235. if has {
  236. return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
  237. }
  238. ctx, committer, err := db.TxContext()
  239. if err != nil {
  240. return err
  241. }
  242. defer committer.Close()
  243. if err = db.Insert(ctx, t); err != nil {
  244. return err
  245. }
  246. // insert units for team
  247. if len(t.Units) > 0 {
  248. for _, unit := range t.Units {
  249. unit.TeamID = t.ID
  250. }
  251. if err = db.Insert(ctx, &t.Units); err != nil {
  252. return err
  253. }
  254. }
  255. // Add all repositories to the team if it has access to all of them.
  256. if t.IncludesAllRepositories {
  257. err = addAllRepositories(ctx, t)
  258. if err != nil {
  259. return fmt.Errorf("addAllRepositories: %v", err)
  260. }
  261. }
  262. // Update organization number of teams.
  263. if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
  264. return err
  265. }
  266. return committer.Commit()
  267. }
  268. // UpdateTeam updates information of team.
  269. func UpdateTeam(t *organization.Team, authChanged, includeAllChanged bool) (err error) {
  270. if len(t.Name) == 0 {
  271. return errors.New("empty team name")
  272. }
  273. if len(t.Description) > 255 {
  274. t.Description = t.Description[:255]
  275. }
  276. ctx, committer, err := db.TxContext()
  277. if err != nil {
  278. return err
  279. }
  280. defer committer.Close()
  281. sess := db.GetEngine(ctx)
  282. t.LowerName = strings.ToLower(t.Name)
  283. has, err := sess.
  284. Where("org_id=?", t.OrgID).
  285. And("lower_name=?", t.LowerName).
  286. And("id!=?", t.ID).
  287. Get(new(organization.Team))
  288. if err != nil {
  289. return err
  290. } else if has {
  291. return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
  292. }
  293. if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
  294. "can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
  295. return fmt.Errorf("update: %v", err)
  296. }
  297. // update units for team
  298. if len(t.Units) > 0 {
  299. for _, unit := range t.Units {
  300. unit.TeamID = t.ID
  301. }
  302. // Delete team-unit.
  303. if _, err := sess.
  304. Where("team_id=?", t.ID).
  305. Delete(new(organization.TeamUnit)); err != nil {
  306. return err
  307. }
  308. if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
  309. return err
  310. }
  311. }
  312. // Update access for team members if needed.
  313. if authChanged {
  314. if err = t.GetRepositoriesCtx(ctx); err != nil {
  315. return fmt.Errorf("getRepositories: %v", err)
  316. }
  317. for _, repo := range t.Repos {
  318. if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
  319. return fmt.Errorf("recalculateTeamAccesses: %v", err)
  320. }
  321. }
  322. }
  323. // Add all repositories to the team if it has access to all of them.
  324. if includeAllChanged && t.IncludesAllRepositories {
  325. err = addAllRepositories(ctx, t)
  326. if err != nil {
  327. return fmt.Errorf("addAllRepositories: %v", err)
  328. }
  329. }
  330. return committer.Commit()
  331. }
  332. // DeleteTeam deletes given team.
  333. // It's caller's responsibility to assign organization ID.
  334. func DeleteTeam(t *organization.Team) error {
  335. ctx, committer, err := db.TxContext()
  336. if err != nil {
  337. return err
  338. }
  339. defer committer.Close()
  340. sess := db.GetEngine(ctx)
  341. if err := t.GetRepositoriesCtx(ctx); err != nil {
  342. return err
  343. }
  344. if err := t.GetMembersCtx(ctx); err != nil {
  345. return err
  346. }
  347. // update branch protections
  348. {
  349. protections := make([]*git_model.ProtectedBranch, 0, 10)
  350. err := sess.In("repo_id",
  351. builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
  352. Find(&protections)
  353. if err != nil {
  354. return fmt.Errorf("findProtectedBranches: %v", err)
  355. }
  356. for _, p := range protections {
  357. var matched1, matched2, matched3 bool
  358. if len(p.WhitelistTeamIDs) != 0 {
  359. p.WhitelistTeamIDs, matched1 = util.RemoveIDFromList(
  360. p.WhitelistTeamIDs, t.ID)
  361. }
  362. if len(p.ApprovalsWhitelistTeamIDs) != 0 {
  363. p.ApprovalsWhitelistTeamIDs, matched2 = util.RemoveIDFromList(
  364. p.ApprovalsWhitelistTeamIDs, t.ID)
  365. }
  366. if len(p.MergeWhitelistTeamIDs) != 0 {
  367. p.MergeWhitelistTeamIDs, matched3 = util.RemoveIDFromList(
  368. p.MergeWhitelistTeamIDs, t.ID)
  369. }
  370. if matched1 || matched2 || matched3 {
  371. if _, err = sess.ID(p.ID).Cols(
  372. "whitelist_team_i_ds",
  373. "merge_whitelist_team_i_ds",
  374. "approvals_whitelist_team_i_ds",
  375. ).Update(p); err != nil {
  376. return fmt.Errorf("updateProtectedBranches: %v", err)
  377. }
  378. }
  379. }
  380. }
  381. if !t.IncludesAllRepositories {
  382. if err := removeAllRepositories(ctx, t); err != nil {
  383. return err
  384. }
  385. }
  386. // Delete team-user.
  387. if _, err := sess.
  388. Where("org_id=?", t.OrgID).
  389. Where("team_id=?", t.ID).
  390. Delete(new(organization.TeamUser)); err != nil {
  391. return err
  392. }
  393. // Delete team-unit.
  394. if _, err := sess.
  395. Where("team_id=?", t.ID).
  396. Delete(new(organization.TeamUnit)); err != nil {
  397. return err
  398. }
  399. // Delete team.
  400. if _, err := sess.ID(t.ID).Delete(new(organization.Team)); err != nil {
  401. return err
  402. }
  403. // Update organization number of teams.
  404. if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
  405. return err
  406. }
  407. return committer.Commit()
  408. }
  409. // AddTeamMember adds new membership of given team to given organization,
  410. // the user will have membership to given organization automatically when needed.
  411. func AddTeamMember(team *organization.Team, userID int64) error {
  412. isAlreadyMember, err := organization.IsTeamMember(db.DefaultContext, team.OrgID, team.ID, userID)
  413. if err != nil || isAlreadyMember {
  414. return err
  415. }
  416. if err := organization.AddOrgUser(team.OrgID, userID); err != nil {
  417. return err
  418. }
  419. ctx, committer, err := db.TxContext()
  420. if err != nil {
  421. return err
  422. }
  423. defer committer.Close()
  424. // check in transaction
  425. isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
  426. if err != nil || isAlreadyMember {
  427. return err
  428. }
  429. sess := db.GetEngine(ctx)
  430. if err := db.Insert(ctx, &organization.TeamUser{
  431. UID: userID,
  432. OrgID: team.OrgID,
  433. TeamID: team.ID,
  434. }); err != nil {
  435. return err
  436. } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil {
  437. return err
  438. }
  439. team.NumMembers++
  440. // Give access to team repositories.
  441. // update exist access if mode become bigger
  442. subQuery := builder.Select("repo_id").From("team_repo").
  443. Where(builder.Eq{"team_id": team.ID})
  444. if _, err := sess.Where("user_id=?", userID).
  445. In("repo_id", subQuery).
  446. And("mode < ?", team.AccessMode).
  447. SetExpr("mode", team.AccessMode).
  448. Update(new(access_model.Access)); err != nil {
  449. return fmt.Errorf("update user accesses: %v", err)
  450. }
  451. // for not exist access
  452. var repoIDs []int64
  453. accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
  454. if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
  455. return fmt.Errorf("select id accesses: %v", err)
  456. }
  457. accesses := make([]*access_model.Access, 0, 100)
  458. for i, repoID := range repoIDs {
  459. accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
  460. if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
  461. if err = db.Insert(ctx, accesses); err != nil {
  462. return fmt.Errorf("insert new user accesses: %v", err)
  463. }
  464. accesses = accesses[:0]
  465. }
  466. }
  467. // watch could be failed, so run it in a goroutine
  468. if setting.Service.AutoWatchNewRepos {
  469. // Get team and its repositories.
  470. if err := team.GetRepositoriesCtx(db.DefaultContext); err != nil {
  471. log.Error("getRepositories failed: %v", err)
  472. }
  473. go func(repos []*repo_model.Repository) {
  474. for _, repo := range repos {
  475. if err = repo_model.WatchRepo(db.DefaultContext, userID, repo.ID, true); err != nil {
  476. log.Error("watch repo failed: %v", err)
  477. }
  478. }
  479. }(team.Repos)
  480. }
  481. return committer.Commit()
  482. }
  483. func removeTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
  484. e := db.GetEngine(ctx)
  485. isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
  486. if err != nil || !isMember {
  487. return err
  488. }
  489. // Check if the user to delete is the last member in owner team.
  490. if team.IsOwnerTeam() && team.NumMembers == 1 {
  491. return organization.ErrLastOrgOwner{UID: userID}
  492. }
  493. team.NumMembers--
  494. if err := team.GetRepositoriesCtx(ctx); err != nil {
  495. return err
  496. }
  497. if _, err := e.Delete(&organization.TeamUser{
  498. UID: userID,
  499. OrgID: team.OrgID,
  500. TeamID: team.ID,
  501. }); err != nil {
  502. return err
  503. } else if _, err = e.
  504. ID(team.ID).
  505. Cols("num_members").
  506. Update(team); err != nil {
  507. return err
  508. }
  509. // Delete access to team repositories.
  510. for _, repo := range team.Repos {
  511. if err := access_model.RecalculateUserAccess(ctx, repo, userID); err != nil {
  512. return err
  513. }
  514. // Remove watches from now unaccessible
  515. if err := reconsiderWatches(ctx, repo, userID); err != nil {
  516. return err
  517. }
  518. // Remove issue assignments from now unaccessible
  519. if err := reconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
  520. return err
  521. }
  522. }
  523. // Check if the user is a member of any team in the organization.
  524. if count, err := e.Count(&organization.TeamUser{
  525. UID: userID,
  526. OrgID: team.OrgID,
  527. }); err != nil {
  528. return err
  529. } else if count == 0 {
  530. return removeOrgUser(ctx, team.OrgID, userID)
  531. }
  532. return nil
  533. }
  534. // RemoveTeamMember removes member from given team of given organization.
  535. func RemoveTeamMember(team *organization.Team, userID int64) error {
  536. ctx, committer, err := db.TxContext()
  537. if err != nil {
  538. return err
  539. }
  540. defer committer.Close()
  541. if err := removeTeamMember(ctx, team, userID); err != nil {
  542. return err
  543. }
  544. return committer.Commit()
  545. }