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

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