| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- // Copyright 2022 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package auth
-
- import (
- "fmt"
- "slices"
- "strings"
-
- "code.gitea.io/gitea/models/perm"
- )
-
- // AccessTokenScopeCategory represents the scope category for an access token
- type AccessTokenScopeCategory int
-
- const (
- AccessTokenScopeCategoryActivityPub AccessTokenScopeCategory = iota
- AccessTokenScopeCategoryAdmin
- AccessTokenScopeCategoryMisc // WARN: this is now just a placeholder, don't remove it which will change the following values
- AccessTokenScopeCategoryNotification
- AccessTokenScopeCategoryOrganization
- AccessTokenScopeCategoryPackage
- AccessTokenScopeCategoryIssue
- AccessTokenScopeCategoryRepository
- AccessTokenScopeCategoryUser
- )
-
- // AllAccessTokenScopeCategories contains all access token scope categories
- var AllAccessTokenScopeCategories = []AccessTokenScopeCategory{
- AccessTokenScopeCategoryActivityPub,
- AccessTokenScopeCategoryAdmin,
- AccessTokenScopeCategoryMisc,
- AccessTokenScopeCategoryNotification,
- AccessTokenScopeCategoryOrganization,
- AccessTokenScopeCategoryPackage,
- AccessTokenScopeCategoryIssue,
- AccessTokenScopeCategoryRepository,
- AccessTokenScopeCategoryUser,
- }
-
- // AccessTokenScopeLevel represents the access levels without a given scope category
- type AccessTokenScopeLevel int
-
- const (
- NoAccess AccessTokenScopeLevel = iota
- Read
- Write
- )
-
- // AccessTokenScope represents the scope for an access token.
- type AccessTokenScope string
-
- // for all categories, write implies read
- const (
- AccessTokenScopeAll AccessTokenScope = "all"
- AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
-
- AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
- AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
-
- AccessTokenScopeReadAdmin AccessTokenScope = "read:admin"
- AccessTokenScopeWriteAdmin AccessTokenScope = "write:admin"
-
- AccessTokenScopeReadMisc AccessTokenScope = "read:misc"
- AccessTokenScopeWriteMisc AccessTokenScope = "write:misc"
-
- AccessTokenScopeReadNotification AccessTokenScope = "read:notification"
- AccessTokenScopeWriteNotification AccessTokenScope = "write:notification"
-
- AccessTokenScopeReadOrganization AccessTokenScope = "read:organization"
- AccessTokenScopeWriteOrganization AccessTokenScope = "write:organization"
-
- AccessTokenScopeReadPackage AccessTokenScope = "read:package"
- AccessTokenScopeWritePackage AccessTokenScope = "write:package"
-
- AccessTokenScopeReadIssue AccessTokenScope = "read:issue"
- AccessTokenScopeWriteIssue AccessTokenScope = "write:issue"
-
- AccessTokenScopeReadRepository AccessTokenScope = "read:repository"
- AccessTokenScopeWriteRepository AccessTokenScope = "write:repository"
-
- AccessTokenScopeReadUser AccessTokenScope = "read:user"
- AccessTokenScopeWriteUser AccessTokenScope = "write:user"
- )
-
- // accessTokenScopeBitmap represents a bitmap of access token scopes.
- type accessTokenScopeBitmap uint64
-
- // Bitmap of each scope, including the child scopes.
- const (
- // AccessTokenScopeAllBits is the bitmap of all access token scopes
- accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits |
- accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits |
- accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits |
- accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits
-
- accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota
-
- accessTokenScopeReadActivityPubBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteActivityPubBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadActivityPubBits
-
- accessTokenScopeReadAdminBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteAdminBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadAdminBits
-
- accessTokenScopeReadMiscBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteMiscBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadMiscBits
-
- accessTokenScopeReadNotificationBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteNotificationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadNotificationBits
-
- accessTokenScopeReadOrganizationBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteOrganizationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadOrganizationBits
-
- accessTokenScopeReadPackageBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWritePackageBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadPackageBits
-
- accessTokenScopeReadIssueBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits
-
- accessTokenScopeReadRepositoryBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits
-
- accessTokenScopeReadUserBits accessTokenScopeBitmap = 1 << iota
- accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits
-
- // The current implementation only supports up to 64 token scopes.
- // If we need to support > 64 scopes,
- // refactoring the whole implementation in this file (and only this file) is needed.
- )
-
- // allAccessTokenScopes contains all access token scopes.
- // The order is important: parent scope must precede child scopes.
- var allAccessTokenScopes = []AccessTokenScope{
- AccessTokenScopePublicOnly,
- AccessTokenScopeWriteActivityPub, AccessTokenScopeReadActivityPub,
- AccessTokenScopeWriteAdmin, AccessTokenScopeReadAdmin,
- AccessTokenScopeWriteMisc, AccessTokenScopeReadMisc,
- AccessTokenScopeWriteNotification, AccessTokenScopeReadNotification,
- AccessTokenScopeWriteOrganization, AccessTokenScopeReadOrganization,
- AccessTokenScopeWritePackage, AccessTokenScopeReadPackage,
- AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue,
- AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository,
- AccessTokenScopeWriteUser, AccessTokenScopeReadUser,
- }
-
- // allAccessTokenScopeBits contains all access token scopes.
- var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
- AccessTokenScopeAll: accessTokenScopeAllBits,
- AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
- AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
- AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
- AccessTokenScopeReadAdmin: accessTokenScopeReadAdminBits,
- AccessTokenScopeWriteAdmin: accessTokenScopeWriteAdminBits,
- AccessTokenScopeReadMisc: accessTokenScopeReadMiscBits,
- AccessTokenScopeWriteMisc: accessTokenScopeWriteMiscBits,
- AccessTokenScopeReadNotification: accessTokenScopeReadNotificationBits,
- AccessTokenScopeWriteNotification: accessTokenScopeWriteNotificationBits,
- AccessTokenScopeReadOrganization: accessTokenScopeReadOrganizationBits,
- AccessTokenScopeWriteOrganization: accessTokenScopeWriteOrganizationBits,
- AccessTokenScopeReadPackage: accessTokenScopeReadPackageBits,
- AccessTokenScopeWritePackage: accessTokenScopeWritePackageBits,
- AccessTokenScopeReadIssue: accessTokenScopeReadIssueBits,
- AccessTokenScopeWriteIssue: accessTokenScopeWriteIssueBits,
- AccessTokenScopeReadRepository: accessTokenScopeReadRepositoryBits,
- AccessTokenScopeWriteRepository: accessTokenScopeWriteRepositoryBits,
- AccessTokenScopeReadUser: accessTokenScopeReadUserBits,
- AccessTokenScopeWriteUser: accessTokenScopeWriteUserBits,
- }
-
- // readAccessTokenScopes maps a scope category to the read permission scope
- var accessTokenScopes = map[AccessTokenScopeLevel]map[AccessTokenScopeCategory]AccessTokenScope{
- Read: {
- AccessTokenScopeCategoryActivityPub: AccessTokenScopeReadActivityPub,
- AccessTokenScopeCategoryAdmin: AccessTokenScopeReadAdmin,
- AccessTokenScopeCategoryMisc: AccessTokenScopeReadMisc,
- AccessTokenScopeCategoryNotification: AccessTokenScopeReadNotification,
- AccessTokenScopeCategoryOrganization: AccessTokenScopeReadOrganization,
- AccessTokenScopeCategoryPackage: AccessTokenScopeReadPackage,
- AccessTokenScopeCategoryIssue: AccessTokenScopeReadIssue,
- AccessTokenScopeCategoryRepository: AccessTokenScopeReadRepository,
- AccessTokenScopeCategoryUser: AccessTokenScopeReadUser,
- },
- Write: {
- AccessTokenScopeCategoryActivityPub: AccessTokenScopeWriteActivityPub,
- AccessTokenScopeCategoryAdmin: AccessTokenScopeWriteAdmin,
- AccessTokenScopeCategoryMisc: AccessTokenScopeWriteMisc,
- AccessTokenScopeCategoryNotification: AccessTokenScopeWriteNotification,
- AccessTokenScopeCategoryOrganization: AccessTokenScopeWriteOrganization,
- AccessTokenScopeCategoryPackage: AccessTokenScopeWritePackage,
- AccessTokenScopeCategoryIssue: AccessTokenScopeWriteIssue,
- AccessTokenScopeCategoryRepository: AccessTokenScopeWriteRepository,
- AccessTokenScopeCategoryUser: AccessTokenScopeWriteUser,
- },
- }
-
- func GetAccessTokenCategories() (res []string) {
- for _, cat := range accessTokenScopes[Read] {
- res = append(res, strings.TrimPrefix(string(cat), "read:"))
- }
- slices.Sort(res)
- return res
- }
-
- // GetRequiredScopes gets the specific scopes for a given level and categories
- func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTokenScopeCategory) []AccessTokenScope {
- scopes := make([]AccessTokenScope, 0, len(scopeCategories))
- for _, cat := range scopeCategories {
- scopes = append(scopes, accessTokenScopes[level][cat])
- }
- return scopes
- }
-
- // ContainsCategory checks if a list of categories contains a specific category
- func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
- return slices.Contains(categories, category)
- }
-
- // GetScopeLevelFromAccessMode converts permission access mode to scope level
- func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel {
- switch mode {
- case perm.AccessModeNone:
- return NoAccess
- case perm.AccessModeRead:
- return Read
- case perm.AccessModeWrite:
- return Write
- case perm.AccessModeAdmin:
- return Write
- case perm.AccessModeOwner:
- return Write
- default:
- return NoAccess
- }
- }
-
- // parse the scope string into a bitmap, thus removing possible duplicates.
- func (s AccessTokenScope) parse() (accessTokenScopeBitmap, error) {
- var bitmap accessTokenScopeBitmap
-
- // The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code
- remainingScopes := string(s)
- for len(remainingScopes) > 0 {
- i := strings.IndexByte(remainingScopes, ',')
- var v string
- if i < 0 {
- v = remainingScopes
- remainingScopes = ""
- } else if i+1 >= len(remainingScopes) {
- v = remainingScopes[:i]
- remainingScopes = ""
- } else {
- v = remainingScopes[:i]
- remainingScopes = remainingScopes[i+1:]
- }
- singleScope := AccessTokenScope(v)
- if singleScope == "" {
- continue
- }
- if singleScope == AccessTokenScopeAll {
- bitmap |= accessTokenScopeAllBits
- continue
- }
-
- bits, ok := allAccessTokenScopeBits[singleScope]
- if !ok {
- return 0, fmt.Errorf("invalid access token scope: %s", singleScope)
- }
- bitmap |= bits
- }
-
- return bitmap, nil
- }
-
- // StringSlice returns the AccessTokenScope as a []string
- func (s AccessTokenScope) StringSlice() []string {
- if s == "" {
- return nil
- }
- return strings.Split(string(s), ",")
- }
-
- // Normalize returns a normalized scope string without any duplicates.
- func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
- bitmap, err := s.parse()
- if err != nil {
- return "", err
- }
-
- return bitmap.toScope(), nil
- }
-
- func (s AccessTokenScope) HasPermissionScope() bool {
- return s != "" && s != AccessTokenScopePublicOnly
- }
-
- // PublicOnly checks if this token scope is limited to public resources
- func (s AccessTokenScope) PublicOnly() (bool, error) {
- bitmap, err := s.parse()
- if err != nil {
- return false, err
- }
-
- return bitmap.hasScope(AccessTokenScopePublicOnly)
- }
-
- // HasScope returns true if the string has the given scope
- func (s AccessTokenScope) HasScope(scopes ...AccessTokenScope) (bool, error) {
- bitmap, err := s.parse()
- if err != nil {
- return false, err
- }
-
- for _, s := range scopes {
- if has, err := bitmap.hasScope(s); !has || err != nil {
- return has, err
- }
- }
-
- return true, nil
- }
-
- // HasAnyScope returns true if any of the scopes is contained in the string
- func (s AccessTokenScope) HasAnyScope(scopes ...AccessTokenScope) (bool, error) {
- bitmap, err := s.parse()
- if err != nil {
- return false, err
- }
-
- for _, s := range scopes {
- if has, err := bitmap.hasScope(s); has || err != nil {
- return has, err
- }
- }
-
- return false, nil
- }
-
- // hasScope returns true if the string has the given scope
- func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
- expectedBits, ok := allAccessTokenScopeBits[scope]
- if !ok {
- return false, fmt.Errorf("invalid access token scope: %s", scope)
- }
-
- return bitmap&expectedBits == expectedBits, nil
- }
-
- // toScope returns a normalized scope string without any duplicates.
- func (bitmap accessTokenScopeBitmap) toScope() AccessTokenScope {
- var scopes []string
-
- // iterate over all scopes, and reconstruct the bitmap
- // if the reconstructed bitmap doesn't change, then the scope is already included
- var reconstruct accessTokenScopeBitmap
-
- for _, singleScope := range allAccessTokenScopes {
- // no need for error checking here, since we know the scope is valid
- if ok, _ := bitmap.hasScope(singleScope); ok {
- current := reconstruct | allAccessTokenScopeBits[singleScope]
- if current == reconstruct {
- continue
- }
-
- reconstruct = current
- scopes = append(scopes, string(singleScope))
- }
- }
-
- scope := AccessTokenScope(strings.Join(scopes, ","))
- scope = AccessTokenScope(strings.ReplaceAll(
- string(scope),
- "write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user",
- "all",
- ))
- return scope
- }
|