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