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.

391 lines
10 KiB

Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
4 years ago
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
4 years ago
Restricted users (#6274) * Restricted users (#4334): initial implementation * Add User.IsRestricted & UI to edit it * Pass user object instead of user id to places where IsRestricted flag matters * Restricted users: maintain access rows for all referenced repos (incl public) * Take logged in user & IsRestricted flag into account in org/repo listings, searches and accesses * Add basic repo access tests for restricted users Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Mention restricted users in the faq Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert unnecessary change `.isUserPartOfOrg` -> `.IsUserPartOfOrg` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Remove unnecessary `org.IsOrganization()` call Signed-off-by: Manush Dodunekov <manush@stendahls.se> * Revert to an `int64` keyed `accessMap` * Add type `userAccess` * Add convenience func updateUserAccess() * Turn accessMap into a `map[int64]userAccess` Signed-off-by: Manush Dodunekov <manush@stendahls.se> * or even better: `map[int64]*userAccess` * updateUserAccess(): use tighter syntax as suggested by lafriks * even tighter * Avoid extra loop * Don't disclose limited orgs to unauthenticated users * Don't assume block only applies to orgs * Use an array of `VisibleType` for filtering * fix yet another thinko * Ok - no need for u * Revert "Ok - no need for u" This reverts commit 5c3e886aabd5acd997a3b35687d322439732c200. Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com> Co-authored-by: Lauris BH <lauris@nix.lv>
4 years ago
  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 models
  5. import (
  6. "fmt"
  7. "code.gitea.io/gitea/modules/log"
  8. )
  9. // Permission contains all the permissions related variables to a repository for a user
  10. type Permission struct {
  11. AccessMode AccessMode
  12. Units []*RepoUnit
  13. UnitsMode map[UnitType]AccessMode
  14. }
  15. // IsOwner returns true if current user is the owner of repository.
  16. func (p *Permission) IsOwner() bool {
  17. return p.AccessMode >= AccessModeOwner
  18. }
  19. // IsAdmin returns true if current user has admin or higher access of repository.
  20. func (p *Permission) IsAdmin() bool {
  21. return p.AccessMode >= AccessModeAdmin
  22. }
  23. // HasAccess returns true if the current user has at least read access to any unit of this repository
  24. func (p *Permission) HasAccess() bool {
  25. if p.UnitsMode == nil {
  26. return p.AccessMode >= AccessModeRead
  27. }
  28. return len(p.UnitsMode) > 0
  29. }
  30. // UnitAccessMode returns current user accessmode to the specify unit of the repository
  31. func (p *Permission) UnitAccessMode(unitType UnitType) AccessMode {
  32. if p.UnitsMode == nil {
  33. for _, u := range p.Units {
  34. if u.Type == unitType {
  35. return p.AccessMode
  36. }
  37. }
  38. return AccessModeNone
  39. }
  40. return p.UnitsMode[unitType]
  41. }
  42. // CanAccess returns true if user has mode access to the unit of the repository
  43. func (p *Permission) CanAccess(mode AccessMode, unitType UnitType) bool {
  44. return p.UnitAccessMode(unitType) >= mode
  45. }
  46. // CanAccessAny returns true if user has mode access to any of the units of the repository
  47. func (p *Permission) CanAccessAny(mode AccessMode, unitTypes ...UnitType) bool {
  48. for _, u := range unitTypes {
  49. if p.CanAccess(mode, u) {
  50. return true
  51. }
  52. }
  53. return false
  54. }
  55. // CanRead returns true if user could read to this unit
  56. func (p *Permission) CanRead(unitType UnitType) bool {
  57. return p.CanAccess(AccessModeRead, unitType)
  58. }
  59. // CanReadAny returns true if user has read access to any of the units of the repository
  60. func (p *Permission) CanReadAny(unitTypes ...UnitType) bool {
  61. return p.CanAccessAny(AccessModeRead, unitTypes...)
  62. }
  63. // CanReadIssuesOrPulls returns true if isPull is true and user could read pull requests and
  64. // returns true if isPull is false and user could read to issues
  65. func (p *Permission) CanReadIssuesOrPulls(isPull bool) bool {
  66. if isPull {
  67. return p.CanRead(UnitTypePullRequests)
  68. }
  69. return p.CanRead(UnitTypeIssues)
  70. }
  71. // CanWrite returns true if user could write to this unit
  72. func (p *Permission) CanWrite(unitType UnitType) bool {
  73. return p.CanAccess(AccessModeWrite, unitType)
  74. }
  75. // CanWriteIssuesOrPulls returns true if isPull is true and user could write to pull requests and
  76. // returns true if isPull is false and user could write to issues
  77. func (p *Permission) CanWriteIssuesOrPulls(isPull bool) bool {
  78. if isPull {
  79. return p.CanWrite(UnitTypePullRequests)
  80. }
  81. return p.CanWrite(UnitTypeIssues)
  82. }
  83. // ColorFormat writes a colored string for these Permissions
  84. func (p *Permission) ColorFormat(s fmt.State) {
  85. noColor := log.ColorBytes(log.Reset)
  86. format := "AccessMode: %-v, %d Units, %d UnitsMode(s): [ "
  87. args := []interface{}{
  88. p.AccessMode,
  89. log.NewColoredValueBytes(len(p.Units), &noColor),
  90. log.NewColoredValueBytes(len(p.UnitsMode), &noColor),
  91. }
  92. if s.Flag('+') {
  93. for i, unit := range p.Units {
  94. config := ""
  95. if unit.Config != nil {
  96. configBytes, err := unit.Config.ToDB()
  97. config = string(configBytes)
  98. if err != nil {
  99. config = err.Error()
  100. }
  101. }
  102. format += "\nUnits[%d]: ID: %d RepoID: %d Type: %-v Config: %s"
  103. args = append(args,
  104. log.NewColoredValueBytes(i, &noColor),
  105. log.NewColoredIDValue(unit.ID),
  106. log.NewColoredIDValue(unit.RepoID),
  107. unit.Type,
  108. config)
  109. }
  110. for key, value := range p.UnitsMode {
  111. format += "\nUnitMode[%-v]: %-v"
  112. args = append(args,
  113. key,
  114. value)
  115. }
  116. } else {
  117. format += "..."
  118. }
  119. format += " ]"
  120. log.ColorFprintf(s, format, args...)
  121. }
  122. // GetUserRepoPermission returns the user permissions to the repository
  123. func GetUserRepoPermission(repo *Repository, user *User) (Permission, error) {
  124. return getUserRepoPermission(x, repo, user)
  125. }
  126. func getUserRepoPermission(e Engine, repo *Repository, user *User) (perm Permission, err error) {
  127. if log.IsTrace() {
  128. defer func() {
  129. if user == nil {
  130. log.Trace("Permission Loaded for anonymous user in %-v:\nPermissions: %-+v",
  131. repo,
  132. perm)
  133. return
  134. }
  135. log.Trace("Permission Loaded for %-v in %-v:\nPermissions: %-+v",
  136. user,
  137. repo,
  138. perm)
  139. }()
  140. }
  141. // anonymous user visit private repo.
  142. // TODO: anonymous user visit public unit of private repo???
  143. if user == nil && repo.IsPrivate {
  144. perm.AccessMode = AccessModeNone
  145. return
  146. }
  147. var isCollaborator bool
  148. if user != nil {
  149. isCollaborator, err = repo.isCollaborator(e, user.ID)
  150. if err != nil {
  151. return perm, err
  152. }
  153. }
  154. if err = repo.getOwner(e); err != nil {
  155. return
  156. }
  157. // Prevent strangers from checking out public repo of private orginization
  158. // Allow user if they are collaborator of a repo within a private orginization but not a member of the orginization itself
  159. if repo.Owner.IsOrganization() && !HasOrgVisible(repo.Owner, user) && !isCollaborator {
  160. perm.AccessMode = AccessModeNone
  161. return
  162. }
  163. if err = repo.getUnits(e); err != nil {
  164. return
  165. }
  166. perm.Units = repo.Units
  167. // anonymous visit public repo
  168. if user == nil {
  169. perm.AccessMode = AccessModeRead
  170. return
  171. }
  172. // Admin or the owner has super access to the repository
  173. if user.IsAdmin || user.ID == repo.OwnerID {
  174. perm.AccessMode = AccessModeOwner
  175. return
  176. }
  177. // plain user
  178. perm.AccessMode, err = accessLevel(e, user, repo)
  179. if err != nil {
  180. return
  181. }
  182. if err = repo.getOwner(e); err != nil {
  183. return
  184. }
  185. if !repo.Owner.IsOrganization() {
  186. return
  187. }
  188. perm.UnitsMode = make(map[UnitType]AccessMode)
  189. // Collaborators on organization
  190. if isCollaborator {
  191. for _, u := range repo.Units {
  192. perm.UnitsMode[u.Type] = perm.AccessMode
  193. }
  194. }
  195. // get units mode from teams
  196. teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID)
  197. if err != nil {
  198. return
  199. }
  200. // if user in an owner team
  201. for _, team := range teams {
  202. if team.Authorize >= AccessModeOwner {
  203. perm.AccessMode = AccessModeOwner
  204. perm.UnitsMode = nil
  205. return
  206. }
  207. }
  208. for _, u := range repo.Units {
  209. var found bool
  210. for _, team := range teams {
  211. if team.unitEnabled(e, u.Type) {
  212. m := perm.UnitsMode[u.Type]
  213. if m < team.Authorize {
  214. perm.UnitsMode[u.Type] = team.Authorize
  215. }
  216. found = true
  217. }
  218. }
  219. // for a public repo on an organization, a non-restricted user has read permission on non-team defined units.
  220. if !found && !repo.IsPrivate && !user.IsRestricted {
  221. if _, ok := perm.UnitsMode[u.Type]; !ok {
  222. perm.UnitsMode[u.Type] = AccessModeRead
  223. }
  224. }
  225. }
  226. // remove no permission units
  227. perm.Units = make([]*RepoUnit, 0, len(repo.Units))
  228. for t := range perm.UnitsMode {
  229. for _, u := range repo.Units {
  230. if u.Type == t {
  231. perm.Units = append(perm.Units, u)
  232. }
  233. }
  234. }
  235. return
  236. }
  237. // IsUserRepoAdmin return true if user has admin right of a repo
  238. func IsUserRepoAdmin(repo *Repository, user *User) (bool, error) {
  239. return isUserRepoAdmin(x, repo, user)
  240. }
  241. func isUserRepoAdmin(e Engine, repo *Repository, user *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(e, user, repo)
  249. if err != nil {
  250. return false, err
  251. }
  252. if mode >= AccessModeAdmin {
  253. return true, nil
  254. }
  255. teams, err := getUserRepoTeams(e, repo.OwnerID, user.ID, repo.ID)
  256. if err != nil {
  257. return false, err
  258. }
  259. for _, team := range teams {
  260. if team.Authorize >= 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(user *User, repo *Repository) (AccessMode, error) {
  269. return accessLevelUnit(x, user, repo, UnitTypeCode)
  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(user *User, repo *Repository, unitType UnitType) (AccessMode, error) {
  274. return accessLevelUnit(x, user, repo, unitType)
  275. }
  276. func accessLevelUnit(e Engine, user *User, repo *Repository, unitType UnitType) (AccessMode, error) {
  277. perm, err := getUserRepoPermission(e, repo, user)
  278. if err != nil {
  279. return AccessModeNone, err
  280. }
  281. return perm.UnitAccessMode(unitType), nil
  282. }
  283. func hasAccessUnit(e Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) {
  284. mode, err := accessLevelUnit(e, user, repo, unitType)
  285. return testMode <= mode, err
  286. }
  287. // HasAccessUnit returns ture if user has testMode to the unit of the repository
  288. func HasAccessUnit(user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) {
  289. return hasAccessUnit(x, user, repo, unitType, testMode)
  290. }
  291. // CanBeAssigned return true if user can be assigned to issue or pull requests in repo
  292. // Currently any write access (code, issues or pr's) is assignable, to match assignee list in user interface.
  293. // FIXME: user could send PullRequest also could be assigned???
  294. func CanBeAssigned(user *User, repo *Repository, isPull bool) (bool, error) {
  295. if user.IsOrganization() {
  296. return false, fmt.Errorf("Organization can't be added as assignee [user_id: %d, repo_id: %d]", user.ID, repo.ID)
  297. }
  298. perm, err := GetUserRepoPermission(repo, user)
  299. if err != nil {
  300. return false, err
  301. }
  302. return perm.CanAccessAny(AccessModeWrite, UnitTypeCode, UnitTypeIssues, UnitTypePullRequests), nil
  303. }
  304. func hasAccess(e Engine, userID int64, repo *Repository) (bool, error) {
  305. var user *User
  306. var err error
  307. if userID > 0 {
  308. user, err = getUserByID(e, userID)
  309. if err != nil {
  310. return false, err
  311. }
  312. }
  313. perm, err := getUserRepoPermission(e, repo, user)
  314. if err != nil {
  315. return false, err
  316. }
  317. return perm.HasAccess(), nil
  318. }
  319. // HasAccess returns true if user has access to repo
  320. func HasAccess(userID int64, repo *Repository) (bool, error) {
  321. return hasAccess(x, userID, repo)
  322. }
  323. // FilterOutRepoIdsWithoutUnitAccess filter out repos where user has no access to repositories
  324. func FilterOutRepoIdsWithoutUnitAccess(u *User, repoIDs []int64, units ...UnitType) ([]int64, error) {
  325. i := 0
  326. for _, rID := range repoIDs {
  327. repo, err := GetRepositoryByID(rID)
  328. if err != nil {
  329. return nil, err
  330. }
  331. perm, err := GetUserRepoPermission(repo, u)
  332. if err != nil {
  333. return nil, err
  334. }
  335. if perm.CanReadAny(units...) {
  336. repoIDs[i] = rID
  337. i++
  338. }
  339. }
  340. return repoIDs[:i], nil
  341. }