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

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