Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

org_team.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  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(ctx context.Context, t *organization.Team) (err error) {
  63. ctx, committer, err := db.TxContext(ctx)
  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(ctx context.Context, t *organization.Team) (err error) {
  75. if t.IncludesAllRepositories {
  76. return nil
  77. }
  78. ctx, committer, err := db.TxContext(ctx)
  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. // NewTeam creates a record of new team.
  127. // It's caller's responsibility to assign organization ID.
  128. func NewTeam(ctx context.Context, t *organization.Team) (err error) {
  129. if len(t.Name) == 0 {
  130. return util.NewInvalidArgumentErrorf("empty team name")
  131. }
  132. if err = organization.IsUsableTeamName(t.Name); err != nil {
  133. return err
  134. }
  135. has, err := db.GetEngine(ctx).ID(t.OrgID).Get(new(user_model.User))
  136. if err != nil {
  137. return err
  138. }
  139. if !has {
  140. return organization.ErrOrgNotExist{ID: t.OrgID}
  141. }
  142. t.LowerName = strings.ToLower(t.Name)
  143. has, err = db.GetEngine(ctx).
  144. Where("org_id=?", t.OrgID).
  145. And("lower_name=?", t.LowerName).
  146. Get(new(organization.Team))
  147. if err != nil {
  148. return err
  149. }
  150. if has {
  151. return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
  152. }
  153. ctx, committer, err := db.TxContext(ctx)
  154. if err != nil {
  155. return err
  156. }
  157. defer committer.Close()
  158. if err = db.Insert(ctx, t); err != nil {
  159. return err
  160. }
  161. // insert units for team
  162. if len(t.Units) > 0 {
  163. for _, unit := range t.Units {
  164. unit.TeamID = t.ID
  165. }
  166. if err = db.Insert(ctx, &t.Units); err != nil {
  167. return err
  168. }
  169. }
  170. // Add all repositories to the team if it has access to all of them.
  171. if t.IncludesAllRepositories {
  172. err = addAllRepositories(ctx, t)
  173. if err != nil {
  174. return fmt.Errorf("addAllRepositories: %w", err)
  175. }
  176. }
  177. // Update organization number of teams.
  178. if _, err = db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
  179. return err
  180. }
  181. return committer.Commit()
  182. }
  183. // UpdateTeam updates information of team.
  184. func UpdateTeam(ctx context.Context, t *organization.Team, authChanged, includeAllChanged bool) (err error) {
  185. if len(t.Name) == 0 {
  186. return util.NewInvalidArgumentErrorf("empty team name")
  187. }
  188. if len(t.Description) > 255 {
  189. t.Description = t.Description[:255]
  190. }
  191. ctx, committer, err := db.TxContext(ctx)
  192. if err != nil {
  193. return err
  194. }
  195. defer committer.Close()
  196. sess := db.GetEngine(ctx)
  197. t.LowerName = strings.ToLower(t.Name)
  198. has, err := sess.
  199. Where("org_id=?", t.OrgID).
  200. And("lower_name=?", t.LowerName).
  201. And("id!=?", t.ID).
  202. Get(new(organization.Team))
  203. if err != nil {
  204. return err
  205. } else if has {
  206. return organization.ErrTeamAlreadyExist{OrgID: t.OrgID, Name: t.LowerName}
  207. }
  208. if _, err = sess.ID(t.ID).Cols("name", "lower_name", "description",
  209. "can_create_org_repo", "authorize", "includes_all_repositories").Update(t); err != nil {
  210. return fmt.Errorf("update: %w", err)
  211. }
  212. // update units for team
  213. if len(t.Units) > 0 {
  214. for _, unit := range t.Units {
  215. unit.TeamID = t.ID
  216. }
  217. // Delete team-unit.
  218. if _, err := sess.
  219. Where("team_id=?", t.ID).
  220. Delete(new(organization.TeamUnit)); err != nil {
  221. return err
  222. }
  223. if _, err = sess.Cols("org_id", "team_id", "type", "access_mode").Insert(&t.Units); err != nil {
  224. return err
  225. }
  226. }
  227. // Update access for team members if needed.
  228. if authChanged {
  229. if err = t.LoadRepositories(ctx); err != nil {
  230. return fmt.Errorf("LoadRepositories: %w", err)
  231. }
  232. for _, repo := range t.Repos {
  233. if err = access_model.RecalculateTeamAccesses(ctx, repo, 0); err != nil {
  234. return fmt.Errorf("recalculateTeamAccesses: %w", err)
  235. }
  236. }
  237. }
  238. // Add all repositories to the team if it has access to all of them.
  239. if includeAllChanged && t.IncludesAllRepositories {
  240. err = addAllRepositories(ctx, t)
  241. if err != nil {
  242. return fmt.Errorf("addAllRepositories: %w", err)
  243. }
  244. }
  245. return committer.Commit()
  246. }
  247. // DeleteTeam deletes given team.
  248. // It's caller's responsibility to assign organization ID.
  249. func DeleteTeam(ctx context.Context, t *organization.Team) error {
  250. ctx, committer, err := db.TxContext(ctx)
  251. if err != nil {
  252. return err
  253. }
  254. defer committer.Close()
  255. if err := t.LoadRepositories(ctx); err != nil {
  256. return err
  257. }
  258. if err := t.LoadMembers(ctx); err != nil {
  259. return err
  260. }
  261. // update branch protections
  262. {
  263. protections := make([]*git_model.ProtectedBranch, 0, 10)
  264. err := db.GetEngine(ctx).In("repo_id",
  265. builder.Select("id").From("repository").Where(builder.Eq{"owner_id": t.OrgID})).
  266. Find(&protections)
  267. if err != nil {
  268. return fmt.Errorf("findProtectedBranches: %w", err)
  269. }
  270. for _, p := range protections {
  271. if err := git_model.RemoveTeamIDFromProtectedBranch(ctx, p, t.ID); err != nil {
  272. return err
  273. }
  274. }
  275. }
  276. if !t.IncludesAllRepositories {
  277. if err := removeAllRepositories(ctx, t); err != nil {
  278. return err
  279. }
  280. }
  281. if err := db.DeleteBeans(ctx,
  282. &organization.Team{ID: t.ID},
  283. &organization.TeamUser{OrgID: t.OrgID, TeamID: t.ID},
  284. &organization.TeamUnit{TeamID: t.ID},
  285. &organization.TeamInvite{TeamID: t.ID},
  286. &issues_model.Review{Type: issues_model.ReviewTypeRequest, ReviewerTeamID: t.ID}, // batch delete the binding relationship between team and PR (request review from team)
  287. ); err != nil {
  288. return err
  289. }
  290. for _, tm := range t.Members {
  291. if err := removeInvalidOrgUser(ctx, tm.ID, t.OrgID); err != nil {
  292. return err
  293. }
  294. }
  295. // Update organization number of teams.
  296. if _, err := db.Exec(ctx, "UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
  297. return err
  298. }
  299. return committer.Commit()
  300. }
  301. // AddTeamMember adds new membership of given team to given organization,
  302. // the user will have membership to given organization automatically when needed.
  303. func AddTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
  304. isAlreadyMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
  305. if err != nil || isAlreadyMember {
  306. return err
  307. }
  308. if err := organization.AddOrgUser(team.OrgID, userID); err != nil {
  309. return err
  310. }
  311. err = db.WithTx(ctx, func(ctx context.Context) error {
  312. // check in transaction
  313. isAlreadyMember, err = organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
  314. if err != nil || isAlreadyMember {
  315. return err
  316. }
  317. sess := db.GetEngine(ctx)
  318. if err := db.Insert(ctx, &organization.TeamUser{
  319. UID: userID,
  320. OrgID: team.OrgID,
  321. TeamID: team.ID,
  322. }); err != nil {
  323. return err
  324. } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(organization.Team)); err != nil {
  325. return err
  326. }
  327. team.NumMembers++
  328. // Give access to team repositories.
  329. // update exist access if mode become bigger
  330. subQuery := builder.Select("repo_id").From("team_repo").
  331. Where(builder.Eq{"team_id": team.ID})
  332. if _, err := sess.Where("user_id=?", userID).
  333. In("repo_id", subQuery).
  334. And("mode < ?", team.AccessMode).
  335. SetExpr("mode", team.AccessMode).
  336. Update(new(access_model.Access)); err != nil {
  337. return fmt.Errorf("update user accesses: %w", err)
  338. }
  339. // for not exist access
  340. var repoIDs []int64
  341. accessSubQuery := builder.Select("repo_id").From("access").Where(builder.Eq{"user_id": userID})
  342. if err := sess.SQL(subQuery.And(builder.NotIn("repo_id", accessSubQuery))).Find(&repoIDs); err != nil {
  343. return fmt.Errorf("select id accesses: %w", err)
  344. }
  345. accesses := make([]*access_model.Access, 0, 100)
  346. for i, repoID := range repoIDs {
  347. accesses = append(accesses, &access_model.Access{RepoID: repoID, UserID: userID, Mode: team.AccessMode})
  348. if (i%100 == 0 || i == len(repoIDs)-1) && len(accesses) > 0 {
  349. if err = db.Insert(ctx, accesses); err != nil {
  350. return fmt.Errorf("insert new user accesses: %w", err)
  351. }
  352. accesses = accesses[:0]
  353. }
  354. }
  355. return nil
  356. })
  357. if err != nil {
  358. return err
  359. }
  360. // this behaviour may spend much time so run it in a goroutine
  361. // FIXME: Update watch repos batchly
  362. if setting.Service.AutoWatchNewRepos {
  363. // Get team and its repositories.
  364. if err := team.LoadRepositories(ctx); err != nil {
  365. log.Error("team.LoadRepositories failed: %v", err)
  366. }
  367. // FIXME: in the goroutine, it can't access the "ctx", it could only use db.DefaultContext at the moment
  368. go func(repos []*repo_model.Repository) {
  369. for _, repo := range repos {
  370. if err = repo_model.WatchRepo(db.DefaultContext, userID, repo.ID, true); err != nil {
  371. log.Error("watch repo failed: %v", err)
  372. }
  373. }
  374. }(team.Repos)
  375. }
  376. return nil
  377. }
  378. func removeTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
  379. e := db.GetEngine(ctx)
  380. isMember, err := organization.IsTeamMember(ctx, team.OrgID, team.ID, userID)
  381. if err != nil || !isMember {
  382. return err
  383. }
  384. // Check if the user to delete is the last member in owner team.
  385. if team.IsOwnerTeam() && team.NumMembers == 1 {
  386. return organization.ErrLastOrgOwner{UID: userID}
  387. }
  388. team.NumMembers--
  389. if err := team.LoadRepositories(ctx); err != nil {
  390. return err
  391. }
  392. if _, err := e.Delete(&organization.TeamUser{
  393. UID: userID,
  394. OrgID: team.OrgID,
  395. TeamID: team.ID,
  396. }); err != nil {
  397. return err
  398. } else if _, err = e.
  399. ID(team.ID).
  400. Cols("num_members").
  401. Update(team); err != nil {
  402. return err
  403. }
  404. // Delete access to team repositories.
  405. for _, repo := range team.Repos {
  406. if err := access_model.RecalculateUserAccess(ctx, repo, userID); err != nil {
  407. return err
  408. }
  409. // Remove watches from now unaccessible
  410. if err := ReconsiderWatches(ctx, repo, userID); err != nil {
  411. return err
  412. }
  413. // Remove issue assignments from now unaccessible
  414. if err := ReconsiderRepoIssuesAssignee(ctx, repo, userID); err != nil {
  415. return err
  416. }
  417. }
  418. return removeInvalidOrgUser(ctx, userID, team.OrgID)
  419. }
  420. func removeInvalidOrgUser(ctx context.Context, userID, orgID int64) error {
  421. // Check if the user is a member of any team in the organization.
  422. if count, err := db.GetEngine(ctx).Count(&organization.TeamUser{
  423. UID: userID,
  424. OrgID: orgID,
  425. }); err != nil {
  426. return err
  427. } else if count == 0 {
  428. return removeOrgUser(ctx, orgID, userID)
  429. }
  430. return nil
  431. }
  432. // RemoveTeamMember removes member from given team of given organization.
  433. func RemoveTeamMember(ctx context.Context, team *organization.Team, userID int64) error {
  434. ctx, committer, err := db.TxContext(ctx)
  435. if err != nil {
  436. return err
  437. }
  438. defer committer.Close()
  439. if err := removeTeamMember(ctx, team, userID); err != nil {
  440. return err
  441. }
  442. return committer.Commit()
  443. }
  444. func ReconsiderRepoIssuesAssignee(ctx context.Context, repo *repo_model.Repository, uid int64) error {
  445. user, err := user_model.GetUserByID(ctx, uid)
  446. if err != nil {
  447. return err
  448. }
  449. if canAssigned, err := access_model.CanBeAssigned(ctx, user, repo, true); err != nil || canAssigned {
  450. return err
  451. }
  452. if _, err := db.GetEngine(ctx).Where(builder.Eq{"assignee_id": uid}).
  453. In("issue_id", builder.Select("id").From("issue").Where(builder.Eq{"repo_id": repo.ID})).
  454. Delete(&issues_model.IssueAssignees{}); err != nil {
  455. return fmt.Errorf("Could not delete assignee[%d] %w", uid, err)
  456. }
  457. return nil
  458. }
  459. func ReconsiderWatches(ctx context.Context, repo *repo_model.Repository, uid int64) error {
  460. if has, err := access_model.HasAccess(ctx, uid, repo); err != nil || has {
  461. return err
  462. }
  463. if err := repo_model.WatchRepo(ctx, uid, repo.ID, false); err != nil {
  464. return err
  465. }
  466. // Remove all IssueWatches a user has subscribed to in the repository
  467. return issues_model.RemoveIssueWatchersByRepoID(ctx, uid, repo.ID)
  468. }