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.

648 lines
15 KiB

  1. // Copyright 2016 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 models
  5. import (
  6. "errors"
  7. "fmt"
  8. "strings"
  9. "code.gitea.io/gitea/modules/log"
  10. "github.com/go-xorm/xorm"
  11. )
  12. const ownerTeamName = "Owners"
  13. // Team represents a organization team.
  14. type Team struct {
  15. ID int64 `xorm:"pk autoincr"`
  16. OrgID int64 `xorm:"INDEX"`
  17. LowerName string
  18. Name string
  19. Description string
  20. Authorize AccessMode
  21. Repos []*Repository `xorm:"-"`
  22. Members []*User `xorm:"-"`
  23. NumRepos int
  24. NumMembers int
  25. UnitTypes []UnitType `xorm:"json"`
  26. }
  27. // GetUnitTypes returns unit types the team owned, empty means all the unit types
  28. func (t *Team) GetUnitTypes() []UnitType {
  29. if len(t.UnitTypes) == 0 {
  30. return allRepUnitTypes
  31. }
  32. return t.UnitTypes
  33. }
  34. // HasWriteAccess returns true if team has at least write level access mode.
  35. func (t *Team) HasWriteAccess() bool {
  36. return t.Authorize >= AccessModeWrite
  37. }
  38. // IsOwnerTeam returns true if team is owner team.
  39. func (t *Team) IsOwnerTeam() bool {
  40. return t.Name == ownerTeamName
  41. }
  42. // IsMember returns true if given user is a member of team.
  43. func (t *Team) IsMember(userID int64) bool {
  44. isMember, err := IsTeamMember(t.OrgID, t.ID, userID)
  45. if err != nil {
  46. log.Error(4, "IsMember: %v", err)
  47. return false
  48. }
  49. return isMember
  50. }
  51. func (t *Team) getRepositories(e Engine) error {
  52. return e.Join("INNER", "team_repo", "repository.id = team_repo.repo_id").
  53. Where("team_repo.team_id=?", t.ID).Find(&t.Repos)
  54. }
  55. // GetRepositories returns all repositories in team of organization.
  56. func (t *Team) GetRepositories() error {
  57. return t.getRepositories(x)
  58. }
  59. func (t *Team) getMembers(e Engine) (err error) {
  60. t.Members, err = getTeamMembers(e, t.ID)
  61. return err
  62. }
  63. // GetMembers returns all members in team of organization.
  64. func (t *Team) GetMembers() (err error) {
  65. return t.getMembers(x)
  66. }
  67. // AddMember adds new membership of the team to the organization,
  68. // the user will have membership to the organization automatically when needed.
  69. func (t *Team) AddMember(userID int64) error {
  70. return AddTeamMember(t, userID)
  71. }
  72. // RemoveMember removes member from team of organization.
  73. func (t *Team) RemoveMember(userID int64) error {
  74. return RemoveTeamMember(t, userID)
  75. }
  76. func (t *Team) hasRepository(e Engine, repoID int64) bool {
  77. return hasTeamRepo(e, t.OrgID, t.ID, repoID)
  78. }
  79. // HasRepository returns true if given repository belong to team.
  80. func (t *Team) HasRepository(repoID int64) bool {
  81. return t.hasRepository(x, repoID)
  82. }
  83. func (t *Team) addRepository(e Engine, repo *Repository) (err error) {
  84. if err = addTeamRepo(e, t.OrgID, t.ID, repo.ID); err != nil {
  85. return err
  86. }
  87. if _, err = e.Incr("num_repos").ID(t.ID).Update(new(Team)); err != nil {
  88. return fmt.Errorf("update team: %v", err)
  89. }
  90. t.NumRepos++
  91. if err = repo.recalculateTeamAccesses(e, 0); err != nil {
  92. return fmt.Errorf("recalculateAccesses: %v", err)
  93. }
  94. if err = t.getMembers(e); err != nil {
  95. return fmt.Errorf("getMembers: %v", err)
  96. }
  97. for _, u := range t.Members {
  98. if err = watchRepo(e, u.ID, repo.ID, true); err != nil {
  99. return fmt.Errorf("watchRepo: %v", err)
  100. }
  101. }
  102. return nil
  103. }
  104. // AddRepository adds new repository to team of organization.
  105. func (t *Team) AddRepository(repo *Repository) (err error) {
  106. if repo.OwnerID != t.OrgID {
  107. return errors.New("Repository does not belong to organization")
  108. } else if t.HasRepository(repo.ID) {
  109. return nil
  110. }
  111. sess := x.NewSession()
  112. defer sess.Close()
  113. if err = sess.Begin(); err != nil {
  114. return err
  115. }
  116. if err = t.addRepository(sess, repo); err != nil {
  117. return err
  118. }
  119. return sess.Commit()
  120. }
  121. func (t *Team) removeRepository(e Engine, repo *Repository, recalculate bool) (err error) {
  122. if err = removeTeamRepo(e, t.ID, repo.ID); err != nil {
  123. return err
  124. }
  125. t.NumRepos--
  126. if _, err = e.ID(t.ID).Cols("num_repos").Update(t); err != nil {
  127. return err
  128. }
  129. // Don't need to recalculate when delete a repository from organization.
  130. if recalculate {
  131. if err = repo.recalculateTeamAccesses(e, t.ID); err != nil {
  132. return err
  133. }
  134. }
  135. teamUsers, err := getTeamUsersByTeamID(e, t.ID)
  136. if err != nil {
  137. return fmt.Errorf("getTeamUsersByTeamID: %v", err)
  138. }
  139. for _, teamUser := range teamUsers {
  140. has, err := hasAccess(e, teamUser.UID, repo, AccessModeRead)
  141. if err != nil {
  142. return err
  143. } else if has {
  144. continue
  145. }
  146. if err = watchRepo(e, teamUser.UID, repo.ID, false); err != nil {
  147. return err
  148. }
  149. }
  150. return nil
  151. }
  152. // RemoveRepository removes repository from team of organization.
  153. func (t *Team) RemoveRepository(repoID int64) error {
  154. if !t.HasRepository(repoID) {
  155. return nil
  156. }
  157. repo, err := GetRepositoryByID(repoID)
  158. if err != nil {
  159. return err
  160. }
  161. sess := x.NewSession()
  162. defer sess.Close()
  163. if err = sess.Begin(); err != nil {
  164. return err
  165. }
  166. if err = t.removeRepository(sess, repo, true); err != nil {
  167. return err
  168. }
  169. return sess.Commit()
  170. }
  171. // UnitEnabled returns if the team has the given unit type enabled
  172. func (t *Team) UnitEnabled(tp UnitType) bool {
  173. if len(t.UnitTypes) == 0 {
  174. return true
  175. }
  176. for _, u := range t.UnitTypes {
  177. if u == tp {
  178. return true
  179. }
  180. }
  181. return false
  182. }
  183. // IsUsableTeamName tests if a name could be as team name
  184. func IsUsableTeamName(name string) error {
  185. switch name {
  186. case "new":
  187. return ErrNameReserved{name}
  188. default:
  189. return nil
  190. }
  191. }
  192. // NewTeam creates a record of new team.
  193. // It's caller's responsibility to assign organization ID.
  194. func NewTeam(t *Team) (err error) {
  195. if len(t.Name) == 0 {
  196. return errors.New("empty team name")
  197. }
  198. if err = IsUsableTeamName(t.Name); err != nil {
  199. return err
  200. }
  201. has, err := x.ID(t.OrgID).Get(new(User))
  202. if err != nil {
  203. return err
  204. } else if !has {
  205. return ErrOrgNotExist{t.OrgID, ""}
  206. }
  207. t.LowerName = strings.ToLower(t.Name)
  208. has, err = x.
  209. Where("org_id=?", t.OrgID).
  210. And("lower_name=?", t.LowerName).
  211. Get(new(Team))
  212. if err != nil {
  213. return err
  214. } else if has {
  215. return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
  216. }
  217. sess := x.NewSession()
  218. defer sess.Close()
  219. if err = sess.Begin(); err != nil {
  220. return err
  221. }
  222. if _, err = sess.Insert(t); err != nil {
  223. sess.Rollback()
  224. return err
  225. }
  226. // Update organization number of teams.
  227. if _, err = sess.Exec("UPDATE `user` SET num_teams=num_teams+1 WHERE id = ?", t.OrgID); err != nil {
  228. sess.Rollback()
  229. return err
  230. }
  231. return sess.Commit()
  232. }
  233. func getTeam(e Engine, orgID int64, name string) (*Team, error) {
  234. t := &Team{
  235. OrgID: orgID,
  236. LowerName: strings.ToLower(name),
  237. }
  238. has, err := e.Get(t)
  239. if err != nil {
  240. return nil, err
  241. } else if !has {
  242. return nil, ErrTeamNotExist
  243. }
  244. return t, nil
  245. }
  246. // GetTeam returns team by given team name and organization.
  247. func GetTeam(orgID int64, name string) (*Team, error) {
  248. return getTeam(x, orgID, name)
  249. }
  250. func getTeamByID(e Engine, teamID int64) (*Team, error) {
  251. t := new(Team)
  252. has, err := e.ID(teamID).Get(t)
  253. if err != nil {
  254. return nil, err
  255. } else if !has {
  256. return nil, ErrTeamNotExist
  257. }
  258. return t, nil
  259. }
  260. // GetTeamByID returns team by given ID.
  261. func GetTeamByID(teamID int64) (*Team, error) {
  262. return getTeamByID(x, teamID)
  263. }
  264. // UpdateTeam updates information of team.
  265. func UpdateTeam(t *Team, authChanged bool) (err error) {
  266. if len(t.Name) == 0 {
  267. return errors.New("empty team name")
  268. }
  269. if len(t.Description) > 255 {
  270. t.Description = t.Description[:255]
  271. }
  272. sess := x.NewSession()
  273. defer sess.Close()
  274. if err = sess.Begin(); err != nil {
  275. return err
  276. }
  277. t.LowerName = strings.ToLower(t.Name)
  278. has, err := sess.
  279. Where("org_id=?", t.OrgID).
  280. And("lower_name=?", t.LowerName).
  281. And("id!=?", t.ID).
  282. Get(new(Team))
  283. if err != nil {
  284. return err
  285. } else if has {
  286. return ErrTeamAlreadyExist{t.OrgID, t.LowerName}
  287. }
  288. if _, err = sess.ID(t.ID).AllCols().Update(t); err != nil {
  289. return fmt.Errorf("update: %v", err)
  290. }
  291. // Update access for team members if needed.
  292. if authChanged {
  293. if err = t.getRepositories(sess); err != nil {
  294. return fmt.Errorf("getRepositories: %v", err)
  295. }
  296. for _, repo := range t.Repos {
  297. if err = repo.recalculateTeamAccesses(sess, 0); err != nil {
  298. return fmt.Errorf("recalculateTeamAccesses: %v", err)
  299. }
  300. }
  301. }
  302. return sess.Commit()
  303. }
  304. // DeleteTeam deletes given team.
  305. // It's caller's responsibility to assign organization ID.
  306. func DeleteTeam(t *Team) error {
  307. if err := t.GetRepositories(); err != nil {
  308. return err
  309. }
  310. sess := x.NewSession()
  311. defer sess.Close()
  312. if err := sess.Begin(); err != nil {
  313. return err
  314. }
  315. // Delete all accesses.
  316. for _, repo := range t.Repos {
  317. if err := repo.recalculateTeamAccesses(sess, t.ID); err != nil {
  318. return err
  319. }
  320. }
  321. // Delete team-repo
  322. if _, err := sess.
  323. Where("team_id=?", t.ID).
  324. Delete(new(TeamRepo)); err != nil {
  325. return err
  326. }
  327. // Delete team-user.
  328. if _, err := sess.
  329. Where("org_id=?", t.OrgID).
  330. Where("team_id=?", t.ID).
  331. Delete(new(TeamUser)); err != nil {
  332. return err
  333. }
  334. // Delete team.
  335. if _, err := sess.ID(t.ID).Delete(new(Team)); err != nil {
  336. return err
  337. }
  338. // Update organization number of teams.
  339. if _, err := sess.Exec("UPDATE `user` SET num_teams=num_teams-1 WHERE id=?", t.OrgID); err != nil {
  340. return err
  341. }
  342. return sess.Commit()
  343. }
  344. // ___________ ____ ___
  345. // \__ ___/___ _____ _____ | | \______ ___________
  346. // | |_/ __ \\__ \ / \| | / ___// __ \_ __ \
  347. // | |\ ___/ / __ \| Y Y \ | /\___ \\ ___/| | \/
  348. // |____| \___ >____ /__|_| /______//____ >\___ >__|
  349. // \/ \/ \/ \/ \/
  350. // TeamUser represents an team-user relation.
  351. type TeamUser struct {
  352. ID int64 `xorm:"pk autoincr"`
  353. OrgID int64 `xorm:"INDEX"`
  354. TeamID int64 `xorm:"UNIQUE(s)"`
  355. UID int64 `xorm:"UNIQUE(s)"`
  356. }
  357. func isTeamMember(e Engine, orgID, teamID, userID int64) (bool, error) {
  358. return e.
  359. Where("org_id=?", orgID).
  360. And("team_id=?", teamID).
  361. And("uid=?", userID).
  362. Table("team_user").
  363. Exist()
  364. }
  365. // IsTeamMember returns true if given user is a member of team.
  366. func IsTeamMember(orgID, teamID, userID int64) (bool, error) {
  367. return isTeamMember(x, orgID, teamID, userID)
  368. }
  369. func getTeamUsersByTeamID(e Engine, teamID int64) ([]*TeamUser, error) {
  370. teamUsers := make([]*TeamUser, 0, 10)
  371. return teamUsers, e.
  372. Where("team_id=?", teamID).
  373. Find(&teamUsers)
  374. }
  375. func getTeamMembers(e Engine, teamID int64) (_ []*User, err error) {
  376. teamUsers, err := getTeamUsersByTeamID(e, teamID)
  377. if err != nil {
  378. return nil, fmt.Errorf("get team-users: %v", err)
  379. }
  380. members := make([]*User, len(teamUsers))
  381. for i, teamUser := range teamUsers {
  382. member, err := getUserByID(e, teamUser.UID)
  383. if err != nil {
  384. return nil, fmt.Errorf("get user '%d': %v", teamUser.UID, err)
  385. }
  386. members[i] = member
  387. }
  388. return members, nil
  389. }
  390. // GetTeamMembers returns all members in given team of organization.
  391. func GetTeamMembers(teamID int64) ([]*User, error) {
  392. return getTeamMembers(x, teamID)
  393. }
  394. func getUserTeams(e Engine, orgID, userID int64) (teams []*Team, err error) {
  395. return teams, e.
  396. Join("INNER", "team_user", "team_user.team_id = team.id").
  397. Where("team.org_id = ?", orgID).
  398. And("team_user.uid=?", userID).
  399. Find(&teams)
  400. }
  401. // GetUserTeams returns all teams that user belongs to in given organization.
  402. func GetUserTeams(orgID, userID int64) ([]*Team, error) {
  403. return getUserTeams(x, orgID, userID)
  404. }
  405. // AddTeamMember adds new membership of given team to given organization,
  406. // the user will have membership to given organization automatically when needed.
  407. func AddTeamMember(team *Team, userID int64) error {
  408. isAlreadyMember, err := IsTeamMember(team.OrgID, team.ID, userID)
  409. if err != nil || isAlreadyMember {
  410. return err
  411. }
  412. if err := AddOrgUser(team.OrgID, userID); err != nil {
  413. return err
  414. }
  415. // Get team and its repositories.
  416. if err := team.GetRepositories(); err != nil {
  417. return err
  418. }
  419. sess := x.NewSession()
  420. defer sess.Close()
  421. if err := sess.Begin(); err != nil {
  422. return err
  423. }
  424. if _, err := sess.Insert(&TeamUser{
  425. UID: userID,
  426. OrgID: team.OrgID,
  427. TeamID: team.ID,
  428. }); err != nil {
  429. return err
  430. } else if _, err := sess.Incr("num_members").ID(team.ID).Update(new(Team)); err != nil {
  431. return err
  432. }
  433. team.NumMembers++
  434. // Give access to team repositories.
  435. for _, repo := range team.Repos {
  436. if err := repo.recalculateTeamAccesses(sess, 0); err != nil {
  437. return err
  438. }
  439. }
  440. return sess.Commit()
  441. }
  442. func removeTeamMember(e *xorm.Session, team *Team, userID int64) error {
  443. isMember, err := isTeamMember(e, team.OrgID, team.ID, userID)
  444. if err != nil || !isMember {
  445. return err
  446. }
  447. // Check if the user to delete is the last member in owner team.
  448. if team.IsOwnerTeam() && team.NumMembers == 1 {
  449. return ErrLastOrgOwner{UID: userID}
  450. }
  451. team.NumMembers--
  452. if err := team.getRepositories(e); err != nil {
  453. return err
  454. }
  455. if _, err := e.Delete(&TeamUser{
  456. UID: userID,
  457. OrgID: team.OrgID,
  458. TeamID: team.ID,
  459. }); err != nil {
  460. return err
  461. } else if _, err = e.
  462. ID(team.ID).
  463. Cols("num_members").
  464. Update(team); err != nil {
  465. return err
  466. }
  467. // Delete access to team repositories.
  468. for _, repo := range team.Repos {
  469. if err := repo.recalculateTeamAccesses(e, 0); err != nil {
  470. return err
  471. }
  472. }
  473. // Check if the user is a member of any team in the organization.
  474. if count, err := e.Count(&TeamUser{
  475. UID: userID,
  476. OrgID: team.OrgID,
  477. }); err != nil {
  478. return err
  479. } else if count == 0 {
  480. return removeOrgUser(e, team.OrgID, userID)
  481. }
  482. return nil
  483. }
  484. // RemoveTeamMember removes member from given team of given organization.
  485. func RemoveTeamMember(team *Team, userID int64) error {
  486. sess := x.NewSession()
  487. defer sess.Close()
  488. if err := sess.Begin(); err != nil {
  489. return err
  490. }
  491. if err := removeTeamMember(sess, team, userID); err != nil {
  492. return err
  493. }
  494. return sess.Commit()
  495. }
  496. // IsUserInTeams returns if a user in some teams
  497. func IsUserInTeams(userID int64, teamIDs []int64) (bool, error) {
  498. return x.Where("uid=?", userID).In("team_id", teamIDs).Exist(new(TeamUser))
  499. }
  500. // ___________ __________
  501. // \__ ___/___ _____ _____\______ \ ____ ______ ____
  502. // | |_/ __ \\__ \ / \| _// __ \\____ \ / _ \
  503. // | |\ ___/ / __ \| Y Y \ | \ ___/| |_> > <_> )
  504. // |____| \___ >____ /__|_| /____|_ /\___ > __/ \____/
  505. // \/ \/ \/ \/ \/|__|
  506. // TeamRepo represents an team-repository relation.
  507. type TeamRepo struct {
  508. ID int64 `xorm:"pk autoincr"`
  509. OrgID int64 `xorm:"INDEX"`
  510. TeamID int64 `xorm:"UNIQUE(s)"`
  511. RepoID int64 `xorm:"UNIQUE(s)"`
  512. }
  513. func hasTeamRepo(e Engine, orgID, teamID, repoID int64) bool {
  514. has, _ := e.
  515. Where("org_id=?", orgID).
  516. And("team_id=?", teamID).
  517. And("repo_id=?", repoID).
  518. Get(new(TeamRepo))
  519. return has
  520. }
  521. // HasTeamRepo returns true if given repository belongs to team.
  522. func HasTeamRepo(orgID, teamID, repoID int64) bool {
  523. return hasTeamRepo(x, orgID, teamID, repoID)
  524. }
  525. func addTeamRepo(e Engine, orgID, teamID, repoID int64) error {
  526. _, err := e.InsertOne(&TeamRepo{
  527. OrgID: orgID,
  528. TeamID: teamID,
  529. RepoID: repoID,
  530. })
  531. return err
  532. }
  533. func removeTeamRepo(e Engine, teamID, repoID int64) error {
  534. _, err := e.Delete(&TeamRepo{
  535. TeamID: teamID,
  536. RepoID: repoID,
  537. })
  538. return err
  539. }
  540. // GetTeamsWithAccessToRepo returns all teams in an organization that have given access level to the repository.
  541. func GetTeamsWithAccessToRepo(orgID, repoID int64, mode AccessMode) ([]*Team, error) {
  542. teams := make([]*Team, 0, 5)
  543. return teams, x.Where("team.authorize >= ?", mode).
  544. Join("INNER", "team_repo", "team_repo.team_id = team.id").
  545. And("team_repo.org_id = ?", orgID).
  546. And("team_repo.repo_id = ?", repoID).
  547. Find(&teams)
  548. }