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

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