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 15KB

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