gitea源码

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. // Copyright 2022 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package auth
  4. import (
  5. "fmt"
  6. "slices"
  7. "strings"
  8. "code.gitea.io/gitea/models/perm"
  9. )
  10. // AccessTokenScopeCategory represents the scope category for an access token
  11. type AccessTokenScopeCategory int
  12. const (
  13. AccessTokenScopeCategoryActivityPub AccessTokenScopeCategory = iota
  14. AccessTokenScopeCategoryAdmin
  15. AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values
  16. AccessTokenScopeCategoryNotification
  17. AccessTokenScopeCategoryOrganization
  18. AccessTokenScopeCategoryPackage
  19. AccessTokenScopeCategoryIssue
  20. AccessTokenScopeCategoryRepository
  21. AccessTokenScopeCategoryUser
  22. )
  23. // AllAccessTokenScopeCategories contains all access token scope categories
  24. var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{
  25. AccessTokenScopeCategoryActivityPub,
  26. AccessTokenScopeCategoryAdmin,
  27. AccessTokenScopeCategoryMisc,
  28. AccessTokenScopeCategoryNotification,
  29. AccessTokenScopeCategoryOrganization,
  30. AccessTokenScopeCategoryPackage,
  31. AccessTokenScopeCategoryIssue,
  32. AccessTokenScopeCategoryRepository,
  33. AccessTokenScopeCategoryUser,
  34. }
  35. // AccessTokenScopeLevel represents the access levels without a given scope category
  36. type AccessTokenScopeLevel int
  37. const (
  38. NoAccess AccessTokenScopeLevel = iota
  39. Read
  40. Write
  41. )
  42. // AccessTokenScope represents the scope for an access token.
  43. type AccessTokenScope string
  44. // for all categories, write implies read
  45. const (
  46. AccessTokenScopeAll AccessTokenScope = "all"
  47. AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
  48. AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
  49. AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
  50. AccessTokenScopeReadAdmin AccessTokenScope = "read:admin"
  51. AccessTokenScopeWriteAdmin AccessTokenScope = "write:admin"
  52. AccessTokenScopeReadMisc AccessTokenScope = "read:misc"
  53. AccessTokenScopeWriteMisc AccessTokenScope = "write:misc"
  54. AccessTokenScopeReadNotification AccessTokenScope = "read:notification"
  55. AccessTokenScopeWriteNotification AccessTokenScope = "write:notification"
  56. AccessTokenScopeReadOrganization AccessTokenScope = "read:organization"
  57. AccessTokenScopeWriteOrganization AccessTokenScope = "write:organization"
  58. AccessTokenScopeReadPackage AccessTokenScope = "read:package"
  59. AccessTokenScopeWritePackage AccessTokenScope = "write:package"
  60. AccessTokenScopeReadIssue AccessTokenScope = "read:issue"
  61. AccessTokenScopeWriteIssue AccessTokenScope = "write:issue"
  62. AccessTokenScopeReadRepository AccessTokenScope = "read:repository"
  63. AccessTokenScopeWriteRepository AccessTokenScope = "write:repository"
  64. AccessTokenScopeReadUser AccessTokenScope = "read:user"
  65. AccessTokenScopeWriteUser AccessTokenScope = "write:user"
  66. )
  67. // accessTokenScopeBitmap represents a bitmap of access token scopes.
  68. type accessTokenScopeBitmap uint64
  69. // Bitmap of each scope, including the child scopes.
  70. const (
  71. // AccessTokenScopeAllBits is the bitmap of all access token scopes
  72. accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits |
  73. accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits |
  74. accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits |
  75. accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits
  76. accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota
  77. accessTokenScopeReadActivityPubBits accessTokenScopeBitmap = 1 << iota
  78. accessTokenScopeWriteActivityPubBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadActivityPubBits
  79. accessTokenScopeReadAdminBits accessTokenScopeBitmap = 1 << iota
  80. accessTokenScopeWriteAdminBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadAdminBits
  81. accessTokenScopeReadMiscBits accessTokenScopeBitmap = 1 << iota
  82. accessTokenScopeWriteMiscBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadMiscBits
  83. accessTokenScopeReadNotificationBits accessTokenScopeBitmap = 1 << iota
  84. accessTokenScopeWriteNotificationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadNotificationBits
  85. accessTokenScopeReadOrganizationBits accessTokenScopeBitmap = 1 << iota
  86. accessTokenScopeWriteOrganizationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadOrganizationBits
  87. accessTokenScopeReadPackageBits accessTokenScopeBitmap = 1 << iota
  88. accessTokenScopeWritePackageBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadPackageBits
  89. accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota
  90. accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits
  91. accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1 << iota
  92. accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits
  93. accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota
  94. accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits
  95. // The current implementation only supports up to 64 token scopes.
  96. // If we need to support > 64 scopes,
  97. // refactoring the whole implementation in this file (and only this file) is needed.
  98. )
  99. // allAccessTokenScopes contains all access token scopes.
  100. // The order is important: parent scope must precede child scopes.
  101. var allAccessTokenScopes = []AccessTokenScope{
  102. AccessTokenScopePublicOnly,
  103. AccessTokenScopeWriteActivityPub, AccessTokenScopeReadActivityPub,
  104. AccessTokenScopeWriteAdmin, AccessTokenScopeReadAdmin,
  105. AccessTokenScopeWriteMisc, AccessTokenScopeReadMisc,
  106. AccessTokenScopeWriteNotification, AccessTokenScopeReadNotification,
  107. AccessTokenScopeWriteOrganization, AccessTokenScopeReadOrganization,
  108. AccessTokenScopeWritePackage, AccessTokenScopeReadPackage,
  109. AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue,
  110. AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository,
  111. AccessTokenScopeWriteUser, AccessTokenScopeReadUser,
  112. }
  113. // allAccessTokenScopeBits contains all access token scopes.
  114. var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
  115. AccessTokenScopeAll: accessTokenScopeAllBits,
  116. AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
  117. AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
  118. AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
  119. AccessTokenScopeReadAdmin: accessTokenScopeReadAdminBits,
  120. AccessTokenScopeWriteAdmin: accessTokenScopeWriteAdminBits,
  121. AccessTokenScopeReadMisc: accessTokenScopeReadMiscBits,
  122. AccessTokenScopeWriteMisc: accessTokenScopeWriteMiscBits,
  123. AccessTokenScopeReadNotification: accessTokenScopeReadNotificationBits,
  124. AccessTokenScopeWriteNotification: accessTokenScopeWriteNotificationBits,
  125. AccessTokenScopeReadOrganization: accessTokenScopeReadOrganizationBits,
  126. AccessTokenScopeWriteOrganization: accessTokenScopeWriteOrganizationBits,
  127. AccessTokenScopeReadPackage: accessTokenScopeReadPackageBits,
  128. AccessTokenScopeWritePackage: accessTokenScopeWritePackageBits,
  129. AccessTokenScopeReadIssue: accessTokenScopeReadIssueBits,
  130. AccessTokenScopeWriteIssue: accessTokenScopeWriteIssueBits,
  131. AccessTokenScopeReadRepository: accessTokenScopeReadRepositoryBits,
  132. AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits,
  133. AccessTokenScopeReadUser: accessTokenScopeReadUserBits,
  134. AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits,
  135. }
  136. // readAccessTokenScopes maps a scope category to the read permission scope
  137. var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]AccessTokenScope{
  138. Read: {
  139. AccessTokenScopeCategoryActivityPub: AccessTokenScopeReadActivityPub,
  140. AccessTokenScopeCategoryAdmin: AccessTokenScopeReadAdmin,
  141. AccessTokenScopeCategoryMisc: AccessTokenScopeReadMisc,
  142. AccessTokenScopeCategoryNotification: AccessTokenScopeReadNotification,
  143. AccessTokenScopeCategoryOrganization: AccessTokenScopeReadOrganization,
  144. AccessTokenScopeCategoryPackage: AccessTokenScopeReadPackage,
  145. AccessTokenScopeCategoryIssue: AccessTokenScopeReadIssue,
  146. AccessTokenScopeCategoryRepository: AccessTokenScopeReadRepository,
  147. AccessTokenScopeCategoryUser: AccessTokenScopeReadUser,
  148. },
  149. Write: {
  150. AccessTokenScopeCategoryActivityPub: AccessTokenScopeWriteActivityPub,
  151. AccessTokenScopeCategoryAdmin: AccessTokenScopeWriteAdmin,
  152. AccessTokenScopeCategoryMisc: AccessTokenScopeWriteMisc,
  153. AccessTokenScopeCategoryNotification: AccessTokenScopeWriteNotification,
  154. AccessTokenScopeCategoryOrganization: AccessTokenScopeWriteOrganization,
  155. AccessTokenScopeCategoryPackage: AccessTokenScopeWritePackage,
  156. AccessTokenScopeCategoryIssue: AccessTokenScopeWriteIssue,
  157. AccessTokenScopeCategoryRepository: AccessTokenScopeWriteRepository,
  158. AccessTokenScopeCategoryUser: AccessTokenScopeWriteUser,
  159. },
  160. }
  161. func GetAccessTokenCategories() (res []string) {
  162. for _, cat := range accessTokenScopes[Read] {
  163. res = append(res, strings.TrimPrefix(string(cat), "read:"))
  164. }
  165. slices.Sort(res)
  166. return res
  167. }
  168. // GetRequiredScopes gets the specific scopes for a given level and categories
  169. func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope {
  170. scopes := make([]AccessTokenScope, 0, len(scopeCategories))
  171. for _, cat := range scopeCategories {
  172. scopes = append(scopes, accessTokenScopes[level][cat])
  173. }
  174. return scopes
  175. }
  176. // ContainsCategory checks if a list of categories contains a specific category
  177. func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
  178. return slices.Contains(categories, category)
  179. }
  180. // GetScopeLevelFromAccessMode converts permission access mode to scope level
  181. func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel {
  182. switch mode {
  183. case perm.AccessModeNone:
  184. return NoAccess
  185. case perm.AccessModeRead:
  186. return Read
  187. case perm.AccessModeWrite:
  188. return Write
  189. case perm.AccessModeAdmin:
  190. return Write
  191. case perm.AccessModeOwner:
  192. return Write
  193. default:
  194. return NoAccess
  195. }
  196. }
  197. // parse the scope string into a bitmap, thus removing possible duplicates.
  198. func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
  199. var bitmap accessTokenScopeBitmap
  200. // The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code
  201. remainingScopes := string(s)
  202. for len(remainingScopes) > 0 {
  203. i := strings.IndexByte(remainingScopes, ',')
  204. var v string
  205. if i < 0 {
  206. v = remainingScopes
  207. remainingScopes = ""
  208. } else if i+1 >= len(remainingScopes) {
  209. v = remainingScopes[:i]
  210. remainingScopes = ""
  211. } else {
  212. v = remainingScopes[:i]
  213. remainingScopes = remainingScopes[i+1:]
  214. }
  215. singleScope := AccessTokenScope(v)
  216. if singleScope == "" {
  217. continue
  218. }
  219. if singleScope == AccessTokenScopeAll {
  220. bitmap |= accessTokenScopeAllBits
  221. continue
  222. }
  223. bits, ok := allAccessTokenScopeBits[singleScope]
  224. if !ok {
  225. return 0, fmt.Errorf("invalid access token scope: %s", singleScope)
  226. }
  227. bitmap |= bits
  228. }
  229. return bitmap, nil
  230. }
  231. // StringSlice returns the AccessTokenScope as a []string
  232. func (s AccessTokenScope) StringSlice() []string {
  233. if s == "" {
  234. return nil
  235. }
  236. return strings.Split(string(s), ",")
  237. }
  238. // Normalize returns a normalized scope string without any duplicates.
  239. func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
  240. bitmap, err := s.parse()
  241. if err != nil {
  242. return "", err
  243. }
  244. return bitmap.toScope(), nil
  245. }
  246. func (s AccessTokenScope) HasPermissionScope() bool {
  247. return s != "" && s != AccessTokenScopePublicOnly
  248. }
  249. // PublicOnly checks if this token scope is limited to public resources
  250. func (s AccessTokenScope) PublicOnly() (bool, error) {
  251. bitmap, err := s.parse()
  252. if err != nil {
  253. return false, err
  254. }
  255. return bitmap.hasScope(AccessTokenScopePublicOnly)
  256. }
  257. // HasScope returns true if the string has the given scope
  258. func (s AccessTokenScope) HasScope(scopes ...AccessTokenScope) (bool, error) {
  259. bitmap, err := s.parse()
  260. if err != nil {
  261. return false, err
  262. }
  263. for _, s := range scopes {
  264. if has, err := bitmap.hasScope(s); !has || err != nil {
  265. return has, err
  266. }
  267. }
  268. return true, nil
  269. }
  270. // HasAnyScope returns true if any of the scopes is contained in the string
  271. func (s AccessTokenScope) HasAnyScope(scopes ...AccessTokenScope) (bool, error) {
  272. bitmap, err := s.parse()
  273. if err != nil {
  274. return false, err
  275. }
  276. for _, s := range scopes {
  277. if has, err := bitmap.hasScope(s); has || err != nil {
  278. return has, err
  279. }
  280. }
  281. return false, nil
  282. }
  283. // hasScope returns true if the string has the given scope
  284. func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
  285. expectedBits, ok := allAccessTokenScopeBits[scope]
  286. if !ok {
  287. return false, fmt.Errorf("invalid access token scope: %s", scope)
  288. }
  289. return bitmap&expectedBits == expectedBits, nil
  290. }
  291. // toScope returns a normalized scope string without any duplicates.
  292. func (bitmap accessTokenScopeBitmap) toScope() AccessTokenScope {
  293. var scopes []string
  294. // iterate over all scopes, and reconstruct the bitmap
  295. // if the reconstructed bitmap doesn't change, then the scope is already included
  296. var reconstruct accessTokenScopeBitmap
  297. for _, singleScope := range allAccessTokenScopes {
  298. // no need for error checking here, since we know the scope is valid
  299. if ok, _ := bitmap.hasScope(singleScope); ok {
  300. current := reconstruct | allAccessTokenScopeBits[singleScope]
  301. if current == reconstruct {
  302. continue
  303. }
  304. reconstruct = current
  305. scopes = append(scopes, string(singleScope))
  306. }
  307. }
  308. scope := AccessTokenScope(strings.Join(scopes, ","))
  309. scope = AccessTokenScope(strings.ReplaceAll(
  310. string(scope),
  311. "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user",
  312. "all",
  313. ))
  314. return scope
  315. }