code.gitea.io/gitea@v1.21.7/models/migrations/v1_20/v259.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package v1_20 //nolint
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  
    10  	"code.gitea.io/gitea/modules/log"
    11  
    12  	"xorm.io/xorm"
    13  )
    14  
    15  // unknownAccessTokenScope represents the scope for an access token that isn't
    16  // known be an old token or a new token.
    17  type unknownAccessTokenScope string
    18  
    19  // AccessTokenScope represents the scope for an access token.
    20  type AccessTokenScope string
    21  
    22  // for all categories, write implies read
    23  const (
    24  	AccessTokenScopeAll        AccessTokenScope = "all"
    25  	AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
    26  
    27  	AccessTokenScopeReadActivityPub  AccessTokenScope = "read:activitypub"
    28  	AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
    29  
    30  	AccessTokenScopeReadAdmin  AccessTokenScope = "read:admin"
    31  	AccessTokenScopeWriteAdmin AccessTokenScope = "write:admin"
    32  
    33  	AccessTokenScopeReadMisc  AccessTokenScope = "read:misc"
    34  	AccessTokenScopeWriteMisc AccessTokenScope = "write:misc"
    35  
    36  	AccessTokenScopeReadNotification  AccessTokenScope = "read:notification"
    37  	AccessTokenScopeWriteNotification AccessTokenScope = "write:notification"
    38  
    39  	AccessTokenScopeReadOrganization  AccessTokenScope = "read:organization"
    40  	AccessTokenScopeWriteOrganization AccessTokenScope = "write:organization"
    41  
    42  	AccessTokenScopeReadPackage  AccessTokenScope = "read:package"
    43  	AccessTokenScopeWritePackage AccessTokenScope = "write:package"
    44  
    45  	AccessTokenScopeReadIssue  AccessTokenScope = "read:issue"
    46  	AccessTokenScopeWriteIssue AccessTokenScope = "write:issue"
    47  
    48  	AccessTokenScopeReadRepository  AccessTokenScope = "read:repository"
    49  	AccessTokenScopeWriteRepository AccessTokenScope = "write:repository"
    50  
    51  	AccessTokenScopeReadUser  AccessTokenScope = "read:user"
    52  	AccessTokenScopeWriteUser AccessTokenScope = "write:user"
    53  )
    54  
    55  // accessTokenScopeBitmap represents a bitmap of access token scopes.
    56  type accessTokenScopeBitmap uint64
    57  
    58  // Bitmap of each scope, including the child scopes.
    59  const (
    60  	// AccessTokenScopeAllBits is the bitmap of all access token scopes
    61  	accessTokenScopeAllBits accessTokenScopeBitmap = accessTokenScopeWriteActivityPubBits |
    62  		accessTokenScopeWriteAdminBits | accessTokenScopeWriteMiscBits | accessTokenScopeWriteNotificationBits |
    63  		accessTokenScopeWriteOrganizationBits | accessTokenScopeWritePackageBits | accessTokenScopeWriteIssueBits |
    64  		accessTokenScopeWriteRepositoryBits | accessTokenScopeWriteUserBits
    65  
    66  	accessTokenScopePublicOnlyBits accessTokenScopeBitmap = 1 << iota
    67  
    68  	accessTokenScopeReadActivityPubBits  accessTokenScopeBitmap = 1 << iota
    69  	accessTokenScopeWriteActivityPubBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadActivityPubBits
    70  
    71  	accessTokenScopeReadAdminBits  accessTokenScopeBitmap = 1 << iota
    72  	accessTokenScopeWriteAdminBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadAdminBits
    73  
    74  	accessTokenScopeReadMiscBits  accessTokenScopeBitmap = 1 << iota
    75  	accessTokenScopeWriteMiscBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadMiscBits
    76  
    77  	accessTokenScopeReadNotificationBits  accessTokenScopeBitmap = 1 << iota
    78  	accessTokenScopeWriteNotificationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadNotificationBits
    79  
    80  	accessTokenScopeReadOrganizationBits  accessTokenScopeBitmap = 1 << iota
    81  	accessTokenScopeWriteOrganizationBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadOrganizationBits
    82  
    83  	accessTokenScopeReadPackageBits  accessTokenScopeBitmap = 1 << iota
    84  	accessTokenScopeWritePackageBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadPackageBits
    85  
    86  	accessTokenScopeReadIssueBits  accessTokenScopeBitmap = 1 << iota
    87  	accessTokenScopeWriteIssueBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadIssueBits
    88  
    89  	accessTokenScopeReadRepositoryBits  accessTokenScopeBitmap = 1 << iota
    90  	accessTokenScopeWriteRepositoryBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadRepositoryBits
    91  
    92  	accessTokenScopeReadUserBits  accessTokenScopeBitmap = 1 << iota
    93  	accessTokenScopeWriteUserBits accessTokenScopeBitmap = 1<<iota | accessTokenScopeReadUserBits
    94  
    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  
   100  // allAccessTokenScopes contains all access token scopes.
   101  // The order is important: parent scope must precede child scopes.
   102  var allAccessTokenScopes = []AccessTokenScope{
   103  	AccessTokenScopePublicOnly,
   104  	AccessTokenScopeWriteActivityPub, AccessTokenScopeReadActivityPub,
   105  	AccessTokenScopeWriteAdmin, AccessTokenScopeReadAdmin,
   106  	AccessTokenScopeWriteMisc, AccessTokenScopeReadMisc,
   107  	AccessTokenScopeWriteNotification, AccessTokenScopeReadNotification,
   108  	AccessTokenScopeWriteOrganization, AccessTokenScopeReadOrganization,
   109  	AccessTokenScopeWritePackage, AccessTokenScopeReadPackage,
   110  	AccessTokenScopeWriteIssue, AccessTokenScopeReadIssue,
   111  	AccessTokenScopeWriteRepository, AccessTokenScopeReadRepository,
   112  	AccessTokenScopeWriteUser, AccessTokenScopeReadUser,
   113  }
   114  
   115  // allAccessTokenScopeBits contains all access token scopes.
   116  var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
   117  	AccessTokenScopeAll:               accessTokenScopeAllBits,
   118  	AccessTokenScopePublicOnly:        accessTokenScopePublicOnlyBits,
   119  	AccessTokenScopeReadActivityPub:   accessTokenScopeReadActivityPubBits,
   120  	AccessTokenScopeWriteActivityPub:  accessTokenScopeWriteActivityPubBits,
   121  	AccessTokenScopeReadAdmin:         accessTokenScopeReadAdminBits,
   122  	AccessTokenScopeWriteAdmin:        accessTokenScopeWriteAdminBits,
   123  	AccessTokenScopeReadMisc:          accessTokenScopeReadMiscBits,
   124  	AccessTokenScopeWriteMisc:         accessTokenScopeWriteMiscBits,
   125  	AccessTokenScopeReadNotification:  accessTokenScopeReadNotificationBits,
   126  	AccessTokenScopeWriteNotification: accessTokenScopeWriteNotificationBits,
   127  	AccessTokenScopeReadOrganization:  accessTokenScopeReadOrganizationBits,
   128  	AccessTokenScopeWriteOrganization: accessTokenScopeWriteOrganizationBits,
   129  	AccessTokenScopeReadPackage:       accessTokenScopeReadPackageBits,
   130  	AccessTokenScopeWritePackage:      accessTokenScopeWritePackageBits,
   131  	AccessTokenScopeReadIssue:         accessTokenScopeReadIssueBits,
   132  	AccessTokenScopeWriteIssue:        accessTokenScopeWriteIssueBits,
   133  	AccessTokenScopeReadRepository:    accessTokenScopeReadRepositoryBits,
   134  	AccessTokenScopeWriteRepository:   accessTokenScopeWriteRepositoryBits,
   135  	AccessTokenScopeReadUser:          accessTokenScopeReadUserBits,
   136  	AccessTokenScopeWriteUser:         accessTokenScopeWriteUserBits,
   137  }
   138  
   139  // hasScope returns true if the string has the given scope
   140  func (bitmap accessTokenScopeBitmap) hasScope(scope AccessTokenScope) (bool, error) {
   141  	expectedBits, ok := allAccessTokenScopeBits[scope]
   142  	if !ok {
   143  		return false, fmt.Errorf("invalid access token scope: %s", scope)
   144  	}
   145  
   146  	return bitmap&expectedBits == expectedBits, nil
   147  }
   148  
   149  // toScope returns a normalized scope string without any duplicates.
   150  func (bitmap accessTokenScopeBitmap) toScope(unknownScopes *[]unknownAccessTokenScope) AccessTokenScope {
   151  	var scopes []string
   152  
   153  	// Preserve unknown scopes, and put them at the beginning so that it's clear
   154  	// when debugging.
   155  	if unknownScopes != nil {
   156  		for _, unknownScope := range *unknownScopes {
   157  			scopes = append(scopes, string(unknownScope))
   158  		}
   159  	}
   160  
   161  	// iterate over all scopes, and reconstruct the bitmap
   162  	// if the reconstructed bitmap doesn't change, then the scope is already included
   163  	var reconstruct accessTokenScopeBitmap
   164  
   165  	for _, singleScope := range allAccessTokenScopes {
   166  		// no need for error checking here, since we know the scope is valid
   167  		if ok, _ := bitmap.hasScope(singleScope); ok {
   168  			current := reconstruct | allAccessTokenScopeBits[singleScope]
   169  			if current == reconstruct {
   170  				continue
   171  			}
   172  
   173  			reconstruct = current
   174  			scopes = append(scopes, string(singleScope))
   175  		}
   176  	}
   177  
   178  	scope := AccessTokenScope(strings.Join(scopes, ","))
   179  	scope = AccessTokenScope(strings.ReplaceAll(
   180  		string(scope),
   181  		"write:activitypub,write:admin,write:misc,write:notification,write:organization,write:package,write:issue,write:repository,write:user",
   182  		"all",
   183  	))
   184  	return scope
   185  }
   186  
   187  // parse the scope string into a bitmap, thus removing possible duplicates.
   188  func (s AccessTokenScope) parse() (accessTokenScopeBitmap, *[]unknownAccessTokenScope) {
   189  	var bitmap accessTokenScopeBitmap
   190  	var unknownScopes []unknownAccessTokenScope
   191  
   192  	// The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code
   193  	remainingScopes := string(s)
   194  	for len(remainingScopes) > 0 {
   195  		i := strings.IndexByte(remainingScopes, ',')
   196  		var v string
   197  		if i < 0 {
   198  			v = remainingScopes
   199  			remainingScopes = ""
   200  		} else if i+1 >= len(remainingScopes) {
   201  			v = remainingScopes[:i]
   202  			remainingScopes = ""
   203  		} else {
   204  			v = remainingScopes[:i]
   205  			remainingScopes = remainingScopes[i+1:]
   206  		}
   207  		singleScope := AccessTokenScope(v)
   208  		if singleScope == "" {
   209  			continue
   210  		}
   211  		if singleScope == AccessTokenScopeAll {
   212  			bitmap |= accessTokenScopeAllBits
   213  			continue
   214  		}
   215  
   216  		bits, ok := allAccessTokenScopeBits[singleScope]
   217  		if !ok {
   218  			unknownScopes = append(unknownScopes, unknownAccessTokenScope(string(singleScope)))
   219  		}
   220  		bitmap |= bits
   221  	}
   222  
   223  	return bitmap, &unknownScopes
   224  }
   225  
   226  // NormalizePreservingUnknown returns a normalized scope string without any
   227  // duplicates.  Unknown scopes are included.
   228  func (s AccessTokenScope) NormalizePreservingUnknown() AccessTokenScope {
   229  	bitmap, unknownScopes := s.parse()
   230  
   231  	return bitmap.toScope(unknownScopes)
   232  }
   233  
   234  // OldAccessTokenScope represents the scope for an access token.
   235  type OldAccessTokenScope string
   236  
   237  const (
   238  	OldAccessTokenScopeAll OldAccessTokenScope = "all"
   239  
   240  	OldAccessTokenScopeRepo       OldAccessTokenScope = "repo"
   241  	OldAccessTokenScopeRepoStatus OldAccessTokenScope = "repo:status"
   242  	OldAccessTokenScopePublicRepo OldAccessTokenScope = "public_repo"
   243  
   244  	OldAccessTokenScopeAdminOrg OldAccessTokenScope = "admin:org"
   245  	OldAccessTokenScopeWriteOrg OldAccessTokenScope = "write:org"
   246  	OldAccessTokenScopeReadOrg  OldAccessTokenScope = "read:org"
   247  
   248  	OldAccessTokenScopeAdminPublicKey OldAccessTokenScope = "admin:public_key"
   249  	OldAccessTokenScopeWritePublicKey OldAccessTokenScope = "write:public_key"
   250  	OldAccessTokenScopeReadPublicKey  OldAccessTokenScope = "read:public_key"
   251  
   252  	OldAccessTokenScopeAdminRepoHook OldAccessTokenScope = "admin:repo_hook"
   253  	OldAccessTokenScopeWriteRepoHook OldAccessTokenScope = "write:repo_hook"
   254  	OldAccessTokenScopeReadRepoHook  OldAccessTokenScope = "read:repo_hook"
   255  
   256  	OldAccessTokenScopeAdminOrgHook OldAccessTokenScope = "admin:org_hook"
   257  
   258  	OldAccessTokenScopeNotification OldAccessTokenScope = "notification"
   259  
   260  	OldAccessTokenScopeUser       OldAccessTokenScope = "user"
   261  	OldAccessTokenScopeReadUser   OldAccessTokenScope = "read:user"
   262  	OldAccessTokenScopeUserEmail  OldAccessTokenScope = "user:email"
   263  	OldAccessTokenScopeUserFollow OldAccessTokenScope = "user:follow"
   264  
   265  	OldAccessTokenScopeDeleteRepo OldAccessTokenScope = "delete_repo"
   266  
   267  	OldAccessTokenScopePackage       OldAccessTokenScope = "package"
   268  	OldAccessTokenScopeWritePackage  OldAccessTokenScope = "write:package"
   269  	OldAccessTokenScopeReadPackage   OldAccessTokenScope = "read:package"
   270  	OldAccessTokenScopeDeletePackage OldAccessTokenScope = "delete:package"
   271  
   272  	OldAccessTokenScopeAdminGPGKey OldAccessTokenScope = "admin:gpg_key"
   273  	OldAccessTokenScopeWriteGPGKey OldAccessTokenScope = "write:gpg_key"
   274  	OldAccessTokenScopeReadGPGKey  OldAccessTokenScope = "read:gpg_key"
   275  
   276  	OldAccessTokenScopeAdminApplication OldAccessTokenScope = "admin:application"
   277  	OldAccessTokenScopeWriteApplication OldAccessTokenScope = "write:application"
   278  	OldAccessTokenScopeReadApplication  OldAccessTokenScope = "read:application"
   279  
   280  	OldAccessTokenScopeSudo OldAccessTokenScope = "sudo"
   281  )
   282  
   283  var accessTokenScopeMap = map[OldAccessTokenScope][]AccessTokenScope{
   284  	OldAccessTokenScopeAll:              {AccessTokenScopeAll},
   285  	OldAccessTokenScopeRepo:             {AccessTokenScopeWriteRepository},
   286  	OldAccessTokenScopeRepoStatus:       {AccessTokenScopeWriteRepository},
   287  	OldAccessTokenScopePublicRepo:       {AccessTokenScopePublicOnly, AccessTokenScopeWriteRepository},
   288  	OldAccessTokenScopeAdminOrg:         {AccessTokenScopeWriteOrganization},
   289  	OldAccessTokenScopeWriteOrg:         {AccessTokenScopeWriteOrganization},
   290  	OldAccessTokenScopeReadOrg:          {AccessTokenScopeReadOrganization},
   291  	OldAccessTokenScopeAdminPublicKey:   {AccessTokenScopeWriteUser},
   292  	OldAccessTokenScopeWritePublicKey:   {AccessTokenScopeWriteUser},
   293  	OldAccessTokenScopeReadPublicKey:    {AccessTokenScopeReadUser},
   294  	OldAccessTokenScopeAdminRepoHook:    {AccessTokenScopeWriteRepository},
   295  	OldAccessTokenScopeWriteRepoHook:    {AccessTokenScopeWriteRepository},
   296  	OldAccessTokenScopeReadRepoHook:     {AccessTokenScopeReadRepository},
   297  	OldAccessTokenScopeAdminOrgHook:     {AccessTokenScopeWriteOrganization},
   298  	OldAccessTokenScopeNotification:     {AccessTokenScopeWriteNotification},
   299  	OldAccessTokenScopeUser:             {AccessTokenScopeWriteUser},
   300  	OldAccessTokenScopeReadUser:         {AccessTokenScopeReadUser},
   301  	OldAccessTokenScopeUserEmail:        {AccessTokenScopeWriteUser},
   302  	OldAccessTokenScopeUserFollow:       {AccessTokenScopeWriteUser},
   303  	OldAccessTokenScopeDeleteRepo:       {AccessTokenScopeWriteRepository},
   304  	OldAccessTokenScopePackage:          {AccessTokenScopeWritePackage},
   305  	OldAccessTokenScopeWritePackage:     {AccessTokenScopeWritePackage},
   306  	OldAccessTokenScopeReadPackage:      {AccessTokenScopeReadPackage},
   307  	OldAccessTokenScopeDeletePackage:    {AccessTokenScopeWritePackage},
   308  	OldAccessTokenScopeAdminGPGKey:      {AccessTokenScopeWriteUser},
   309  	OldAccessTokenScopeWriteGPGKey:      {AccessTokenScopeWriteUser},
   310  	OldAccessTokenScopeReadGPGKey:       {AccessTokenScopeReadUser},
   311  	OldAccessTokenScopeAdminApplication: {AccessTokenScopeWriteUser},
   312  	OldAccessTokenScopeWriteApplication: {AccessTokenScopeWriteUser},
   313  	OldAccessTokenScopeReadApplication:  {AccessTokenScopeReadUser},
   314  	OldAccessTokenScopeSudo:             {AccessTokenScopeWriteAdmin},
   315  }
   316  
   317  type AccessToken struct {
   318  	ID    int64 `xorm:"pk autoincr"`
   319  	Scope string
   320  }
   321  
   322  func ConvertScopedAccessTokens(x *xorm.Engine) error {
   323  	var tokens []*AccessToken
   324  
   325  	if err := x.Find(&tokens); err != nil {
   326  		return err
   327  	}
   328  
   329  	for _, token := range tokens {
   330  		var scopes []string
   331  		allNewScopesMap := make(map[AccessTokenScope]bool)
   332  		for _, oldScope := range strings.Split(token.Scope, ",") {
   333  			if newScopes, exists := accessTokenScopeMap[OldAccessTokenScope(oldScope)]; exists {
   334  				for _, newScope := range newScopes {
   335  					allNewScopesMap[newScope] = true
   336  				}
   337  			} else {
   338  				log.Debug("access token scope not recognized as old token scope %s; preserving it", oldScope)
   339  				scopes = append(scopes, oldScope)
   340  			}
   341  		}
   342  
   343  		for s := range allNewScopesMap {
   344  			scopes = append(scopes, string(s))
   345  		}
   346  		scope := AccessTokenScope(strings.Join(scopes, ","))
   347  
   348  		// normalize the scope
   349  		normScope := scope.NormalizePreservingUnknown()
   350  
   351  		token.Scope = string(normScope)
   352  
   353  		// update the db entry with the new scope
   354  		if _, err := x.Cols("scope").ID(token.ID).Update(token); err != nil {
   355  			return err
   356  		}
   357  	}
   358  
   359  	return nil
   360  }