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

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