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.

repo_permission.go 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package access
  4. import (
  5. "context"
  6. "fmt"
  7. "slices"
  8. "code.gitea.io/gitea/models/db"
  9. "code.gitea.io/gitea/models/organization"
  10. perm_model "code.gitea.io/gitea/models/perm"
  11. repo_model "code.gitea.io/gitea/models/repo"
  12. "code.gitea.io/gitea/models/unit"
  13. user_model "code.gitea.io/gitea/models/user"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/util"
  16. )
  17. // Permission contains all the permissions related variables to a repository for a user
  18. type Permission struct {
  19. AccessMode perm_model.AccessMode
  20. units []*repo_model.RepoUnit
  21. unitsMode map[unit.Type]perm_model.AccessMode
  22. everyoneAccessMode map[unit.Type]perm_model.AccessMode
  23. }
  24. // IsOwner returns true if current user is the owner of repository.
  25. func (p *Permission) IsOwner() bool {
  26. return p.AccessMode >= perm_model.AccessModeOwner
  27. }
  28. // IsAdmin returns true if current user has admin or higher access of repository.
  29. func (p *Permission) IsAdmin() bool {
  30. return p.AccessMode >= perm_model.AccessModeAdmin
  31. }
  32. // HasAnyUnitAccess returns true if the user might have at least one access mode to any unit of this repository.
  33. // It doesn't count the "everyone access mode".
  34. func (p *Permission) HasAnyUnitAccess() bool {
  35. for _, v := range p.unitsMode {
  36. if v >= perm_model.AccessModeRead {
  37. return true
  38. }
  39. }
  40. return p.AccessMode >= perm_model.AccessModeRead
  41. }
  42. func (p *Permission) HasAnyUnitAccessOrEveryoneAccess() bool {
  43. for _, v := range p.everyoneAccessMode {
  44. if v >= perm_model.AccessModeRead {
  45. return true
  46. }
  47. }
  48. return p.HasAnyUnitAccess()
  49. }
  50. // HasUnits returns true if the permission contains attached units
  51. func (p *Permission) HasUnits() bool {
  52. return len(p.units) > 0
  53. }
  54. // GetFirstUnitRepoID returns the repo ID of the first unit, it is a fragile design and should NOT be used anymore
  55. // deprecated
  56. func (p *Permission) GetFirstUnitRepoID() int64 {
  57. if len(p.units) > 0 {
  58. return p.units[0].RepoID
  59. }
  60. return 0
  61. }
  62. // UnitAccessMode returns current user access mode to the specify unit of the repository
  63. // It also considers "everyone access mode"
  64. func (p *Permission) UnitAccessMode(unitType unit.Type) perm_model.AccessMode {
  65. // if the units map contains the access mode, use it, but admin/owner mode could override it
  66. if m, ok := p.unitsMode[unitType]; ok {
  67. return util.Iif(p.AccessMode >= perm_model.AccessModeAdmin, p.AccessMode, m)
  68. }
  69. // if the units map does not contain the access mode, return the default access mode if the unit exists
  70. unitDefaultAccessMode := max(p.AccessMode, p.everyoneAccessMode[unitType])
  71. hasUnit := slices.ContainsFunc(p.units, func(u *repo_model.RepoUnit) bool { return u.Type == unitType })
  72. return util.Iif(hasUnit, unitDefaultAccessMode, perm_model.AccessModeNone)
  73. }
  74. func (p *Permission) SetUnitsWithDefaultAccessMode(units []*repo_model.RepoUnit, mode perm_model.AccessMode) {
  75. p.units = units
  76. p.unitsMode = make(map[unit.Type]perm_model.AccessMode)
  77. for _, u := range p.units {
  78. p.unitsMode[u.Type] = mode
  79. }
  80. }
  81. // CanAccess returns true if user has mode access to the unit of the repository
  82. func (p *Permission) CanAccess(mode perm_model.AccessMode, unitType unit.Type) bool {
  83. return p.UnitAccessMode(unitType) >= mode
  84. }
  85. // CanAccessAny returns true if user has mode access to any of the units of the repository
  86. func (p *Permission) CanAccessAny(mode perm_model.AccessMode, unitTypes ...unit.Type) bool {
  87. for _, u := range unitTypes {
  88. if p.CanAccess(mode, u) {
  89. return true
  90. }
  91. }
  92. return false
  93. }
  94. // CanRead returns true if user could read to this unit
  95. func (p *Permission) CanRead(unitType unit.Type) bool {
  96. return p.CanAccess(perm_model.AccessModeRead, unitType)
  97. }
  98. // CanReadAny returns true if user has read access to any of the units of the repository
  99. func (p *Permission) CanReadAny(unitTypes ...unit.Type) bool {
  100. return p.CanAccessAny(perm_model.AccessModeRead, unitTypes...)
  101. }
  102. // CanReadIssuesOrPulls returns true if isPull is true and user could read pull requests and
  103. // returns true if isPull is false and user could read to issues
  104. func (p *Permission) CanReadIssuesOrPulls(isPull bool) bool {
  105. if isPull {
  106. return p.CanRead(unit.TypePullRequests)
  107. }
  108. return p.CanRead(unit.TypeIssues)
  109. }
  110. // CanWrite returns true if user could write to this unit
  111. func (p *Permission) CanWrite(unitType unit.Type) bool {
  112. return p.CanAccess(perm_model.AccessModeWrite, unitType)
  113. }
  114. // CanWriteIssuesOrPulls returns true if isPull is true and user could write to pull requests and
  115. // returns true if isPull is false and user could write to issues
  116. func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
  117. if isPull {
  118. return p.CanWrite(unit.TypePullRequests)
  119. }
  120. return p.CanWrite(unit.TypeIssues)
  121. }
  122. func (p *Permission) ReadableUnitTypes() []unit.Type {
  123. types := make([]unit.Type, 0, len(p.units))
  124. for _, u := range p.units {
  125. if p.CanRead(u.Type) {
  126. types = append(types, u.Type)
  127. }
  128. }
  129. return types
  130. }
  131. func (p *Permission) LogString() string {
  132. format := "<Permission AccessMode=%s, %d Units, %d UnitsMode(s): [ "
  133. args := []any{p.AccessMode.ToString(), len(p.units), len(p.unitsMode)}
  134. for i, u := range p.units {
  135. config := ""
  136. if u.Config != nil {
  137. configBytes, err := u.Config.ToDB()
  138. config = string(configBytes)
  139. if err != nil {
  140. config = err.Error()
  141. }
  142. }
  143. format += "\nUnits[%d]: ID: %d RepoID: %d Type: %s Config: %s"
  144. args = append(args, i, u.ID, u.RepoID, u.Type.LogString(), config)
  145. }
  146. for key, value := range p.unitsMode {
  147. format += "\nUnitMode[%-v]: %-v"
  148. args = append(args, key.LogString(), value.LogString())
  149. }
  150. format += " ]>"
  151. return fmt.Sprintf(format, args...)
  152. }
  153. func applyEveryoneRepoPermission(user *user_model.User, perm *Permission) {
  154. if user == nil || user.ID <= 0 {
  155. return
  156. }
  157. for _, u := range perm.units {
  158. if u.EveryoneAccessMode >= perm_model.AccessModeRead && u.EveryoneAccessMode > perm.everyoneAccessMode[u.Type] {
  159. if perm.everyoneAccessMode == nil {
  160. perm.everyoneAccessMode = make(map[unit.Type]perm_model.AccessMode)
  161. }
  162. perm.everyoneAccessMode[u.Type] = u.EveryoneAccessMode
  163. }
  164. }
  165. }
  166. // GetUserRepoPermission returns the user permissions to the repository
  167. func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (perm Permission, err error) {
  168. defer func() {
  169. if err == nil {
  170. applyEveryoneRepoPermission(user, &perm)
  171. }
  172. if log.IsTrace() {
  173. log.Trace("Permission Loaded for user %-v in repo %-v, permissions: %-+v", user, repo, perm)
  174. }
  175. }()
  176. if err = repo.LoadUnits(ctx); err != nil {
  177. return perm, err
  178. }
  179. perm.units = repo.Units
  180. // anonymous user visit private repo.
  181. // TODO: anonymous user visit public unit of private repo???
  182. if user == nil && repo.IsPrivate {
  183. perm.AccessMode = perm_model.AccessModeNone
  184. return perm, nil
  185. }
  186. var isCollaborator bool
  187. if user != nil {
  188. isCollaborator, err = repo_model.IsCollaborator(ctx, repo.ID, user.ID)
  189. if err != nil {
  190. return perm, err
  191. }
  192. }
  193. if err = repo.LoadOwner(ctx); err != nil {
  194. return perm, err
  195. }
  196. // Prevent strangers from checking out public repo of private organization/users
  197. // Allow user if they are collaborator of a repo within a private user or a private organization but not a member of the organization itself
  198. if !organization.HasOrgOrUserVisible(ctx, repo.Owner, user) && !isCollaborator {
  199. perm.AccessMode = perm_model.AccessModeNone
  200. return perm, nil
  201. }
  202. // anonymous visit public repo
  203. if user == nil {
  204. perm.AccessMode = perm_model.AccessModeRead
  205. return perm, nil
  206. }
  207. // Admin or the owner has super access to the repository
  208. if user.IsAdmin || user.ID == repo.OwnerID {
  209. perm.AccessMode = perm_model.AccessModeOwner
  210. return perm, nil
  211. }
  212. // plain user
  213. perm.AccessMode, err = accessLevel(ctx, user, repo)
  214. if err != nil {
  215. return perm, err
  216. }
  217. if !repo.Owner.IsOrganization() {
  218. return perm, nil
  219. }
  220. perm.unitsMode = make(map[unit.Type]perm_model.AccessMode)
  221. // Collaborators on organization
  222. if isCollaborator {
  223. for _, u := range repo.Units {
  224. perm.unitsMode[u.Type] = perm.AccessMode
  225. }
  226. }
  227. // get units mode from teams
  228. teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
  229. if err != nil {
  230. return perm, err
  231. }
  232. // if user in an owner team
  233. for _, team := range teams {
  234. if team.AccessMode >= perm_model.AccessModeAdmin {
  235. perm.AccessMode = perm_model.AccessModeOwner
  236. perm.unitsMode = nil
  237. return perm, nil
  238. }
  239. }
  240. for _, u := range repo.Units {
  241. var found bool
  242. for _, team := range teams {
  243. if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
  244. perm.unitsMode[u.Type] = max(perm.unitsMode[u.Type], teamMode)
  245. found = true
  246. }
  247. }
  248. // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
  249. if !found && !repo.IsPrivate && !user.IsRestricted {
  250. if _, ok := perm.unitsMode[u.Type]; !ok {
  251. perm.unitsMode[u.Type] = perm_model.AccessModeRead
  252. }
  253. }
  254. }
  255. // remove no permission units
  256. perm.units = make([]*repo_model.RepoUnit, 0, len(repo.Units))
  257. for t := range perm.unitsMode {
  258. for _, u := range repo.Units {
  259. if u.Type == t {
  260. perm.units = append(perm.units, u)
  261. }
  262. }
  263. }
  264. return perm, err
  265. }
  266. // IsUserRealRepoAdmin check if this user is real repo admin
  267. func IsUserRealRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
  268. if repo.OwnerID == user.ID {
  269. return true, nil
  270. }
  271. if err := repo.LoadOwner(ctx); err != nil {
  272. return false, err
  273. }
  274. accessMode, err := accessLevel(ctx, user, repo)
  275. if err != nil {
  276. return false, err
  277. }
  278. return accessMode >= perm_model.AccessModeAdmin, nil
  279. }
  280. // IsUserRepoAdmin return true if user has admin right of a repo
  281. func IsUserRepoAdmin(ctx context.Context, repo *repo_model.Repository, user *user_model.User) (bool, error) {
  282. if user == nil || repo == nil {
  283. return false, nil
  284. }
  285. if user.IsAdmin {
  286. return true, nil
  287. }
  288. mode, err := accessLevel(ctx, user, repo)
  289. if err != nil {
  290. return false, err
  291. }
  292. if mode >= perm_model.AccessModeAdmin {
  293. return true, nil
  294. }
  295. teams, err := organization.GetUserRepoTeams(ctx, repo.OwnerID, user.ID, repo.ID)
  296. if err != nil {
  297. return false, err
  298. }
  299. for _, team := range teams {
  300. if team.AccessMode >= perm_model.AccessModeAdmin {
  301. return true, nil
  302. }
  303. }
  304. return false, nil
  305. }
  306. // AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
  307. // user does not have access.
  308. func AccessLevel(ctx context.Context, user *user_model.User, repo *repo_model.Repository) (perm_model.AccessMode, error) { //nolint
  309. return AccessLevelUnit(ctx, user, repo, unit.TypeCode)
  310. }
  311. // AccessLevelUnit returns the Access a user has to a repository's. Will return NoneAccess if the
  312. // user does not have access.
  313. func AccessLevelUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type) (perm_model.AccessMode, error) { //nolint
  314. perm, err := GetUserRepoPermission(ctx, repo, user)
  315. if err != nil {
  316. return perm_model.AccessModeNone, err
  317. }
  318. return perm.UnitAccessMode(unitType), nil
  319. }
  320. // HasAccessUnit returns true if user has testMode to the unit of the repository
  321. func HasAccessUnit(ctx context.Context, user *user_model.User, repo *repo_model.Repository, unitType unit.Type, testMode perm_model.AccessMode) (bool, error) {
  322. mode, err := AccessLevelUnit(ctx, user, repo, unitType)
  323. return testMode <= mode, err
  324. }
  325. // CanBeAssigned return true if user can be assigned to issue or pull requests in repo
  326. // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
  327. func CanBeAssigned(ctx context.Context, user *user_model.User, repo *repo_model.Repository, _ bool) (bool, error) {
  328. if user.IsOrganization() {
  329. return false, fmt.Errorf("organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
  330. }
  331. perm, err := GetUserRepoPermission(ctx, repo, user)
  332. if err != nil {
  333. return false, err
  334. }
  335. return perm.CanAccessAny(perm_model.AccessModeWrite, unit.AllRepoUnitTypes...) ||
  336. perm.CanAccessAny(perm_model.AccessModeRead, unit.TypePullRequests), nil
  337. }
  338. // HasAnyUnitAccess see the comment of "perm.HasAnyUnitAccess"
  339. func HasAnyUnitAccess(ctx context.Context, userID int64, repo *repo_model.Repository) (bool, error) {
  340. var user *user_model.User
  341. var err error
  342. if userID > 0 {
  343. user, err = user_model.GetUserByID(ctx, userID)
  344. if err != nil {
  345. return false, err
  346. }
  347. }
  348. perm, err := GetUserRepoPermission(ctx, repo, user)
  349. if err != nil {
  350. return false, err
  351. }
  352. return perm.HasAnyUnitAccess(), nil
  353. }
  354. // getUsersWithAccessMode returns users that have at least given access mode to the repository.
  355. func getUsersWithAccessMode(ctx context.Context, repo *repo_model.Repository, mode perm_model.AccessMode) (_ []*user_model.User, err error) {
  356. if err = repo.LoadOwner(ctx); err != nil {
  357. return nil, err
  358. }
  359. e := db.GetEngine(ctx)
  360. accesses := make([]*Access, 0, 10)
  361. if err = e.Where("repo_id = ? AND mode >= ?", repo.ID, mode).Find(&accesses); err != nil {
  362. return nil, err
  363. }
  364. // Leave a seat for owner itself to append later, but if owner is an organization
  365. // and just waste 1 unit is cheaper than re-allocate memory once.
  366. users := make([]*user_model.User, 0, len(accesses)+1)
  367. if len(accesses) > 0 {
  368. userIDs := make([]int64, len(accesses))
  369. for i := 0; i < len(accesses); i++ {
  370. userIDs[i] = accesses[i].UserID
  371. }
  372. if err = e.In("id", userIDs).Find(&users); err != nil {
  373. return nil, err
  374. }
  375. }
  376. if !repo.Owner.IsOrganization() {
  377. users = append(users, repo.Owner)
  378. }
  379. return users, nil
  380. }
  381. // GetRepoReaders returns all users that have explicit read access or higher to the repository.
  382. func GetRepoReaders(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
  383. return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeRead)
  384. }
  385. // GetRepoWriters returns all users that have write access to the repository.
  386. func GetRepoWriters(ctx context.Context, repo *repo_model.Repository) (_ []*user_model.User, err error) {
  387. return getUsersWithAccessMode(ctx, repo, perm_model.AccessModeWrite)
  388. }
  389. // IsRepoReader returns true if user has explicit read access or higher to the repository.
  390. func IsRepoReader(ctx context.Context, repo *repo_model.Repository, userID int64) (bool, error) {
  391. if repo.OwnerID == userID {
  392. return true, nil
  393. }
  394. return db.GetEngine(ctx).Where("repo_id = ? AND user_id = ? AND mode >= ?", repo.ID, userID, perm_model.AccessModeRead).Get(&Access{})
  395. }
  396. // CheckRepoUnitUser check whether user could visit the unit of this repository
  397. func CheckRepoUnitUser(ctx context.Context, repo *repo_model.Repository, user *user_model.User, unitType unit.Type) bool {
  398. if user != nil && user.IsAdmin {
  399. return true
  400. }
  401. perm, err := GetUserRepoPermission(ctx, repo, user)
  402. if err != nil {
  403. log.Error("GetUserRepoPermission: %w", err)
  404. return false
  405. }
  406. return perm.CanRead(unitType)
  407. }