mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-26 17:08:25 +00:00 
			
		
		
		
	Closes #27455 > The mechanism responsible for long-term authentication (the 'remember me' cookie) uses a weak construction technique. It will hash the user's hashed password and the rands value; it will then call the secure cookie code, which will encrypt the user's name with the computed hash. If one were able to dump the database, they could extract those two values to rebuild that cookie and impersonate a user. That vulnerability exists from the date the dump was obtained until a user changed their password. > > To fix this security issue, the cookie could be created and verified using a different technique such as the one explained at https://paragonie.com/blog/2015/04/secure-authentication-php-with-long-term-persistence#secure-remember-me-cookies. The PR removes the now obsolete setting `COOKIE_USERNAME`.
		
			
				
	
	
		
			351 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			351 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2022 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package auth
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"strings"
 | |
| 
 | |
| 	"code.gitea.io/gitea/models/perm"
 | |
| )
 | |
| 
 | |
| // AccessTokenScopeCategory represents the scope category for an access token
 | |
| type AccessTokenScopeCategory int
 | |
| 
 | |
| const (
 | |
| 	AccessTokenScopeCategoryActivityPub = 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,
 | |
| 	},
 | |
| }
 | |
| 
 | |
| // 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 {
 | |
| 	for _, c := range categories {
 | |
| 		if c == category {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| // 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 {
 | |
| 	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
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 | |
| 
 | |
| // 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
 | |
| }
 |