gitea源码

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