Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support scoped access tokens #20908

Merged
merged 152 commits into from
Jan 17, 2023
Merged
Show file tree
Hide file tree
Changes from 133 commits
Commits
Show all changes
152 commits
Select commit Hold shift + click to select a range
90e3b58
Add scope field to access token
harryzcy Aug 22, 2022
89d1140
Add token scope helper functions
harryzcy Aug 26, 2022
0933496
Add comments
harryzcy Sep 6, 2022
948cd14
Move token_scope file to models/auth
harryzcy Sep 6, 2022
fd6e821
Include copyright
harryzcy Sep 6, 2022
b013747
Add more unit tests
harryzcy Sep 6, 2022
794484a
Support 'all' scope
harryzcy Sep 6, 2022
ad3ff54
Support checking scope access
harryzcy Sep 6, 2022
6c5dd10
Restrict scope for some APIs
harryzcy Sep 6, 2022
cd13850
Let repo scope to cover admin:repo_hook
harryzcy Sep 6, 2022
745ec98
Add sudo scope
harryzcy Sep 6, 2022
74b4871
Add more scope requirements
harryzcy Sep 6, 2022
6f4eed0
Add token selection UI
harryzcy Sep 7, 2022
4fd1722
Update access token form
harryzcy Sep 7, 2022
4476e1d
Fix integration tests for api_branch
harryzcy Sep 7, 2022
86341c5
Fix ending newline for a template
harryzcy Sep 7, 2022
cfcbdba
Update the types for ApiTokenScope in context
harryzcy Sep 7, 2022
4b570b8
Parse scope for request when adding a token
harryzcy Sep 7, 2022
1ebfc83
Fix GetScope method
harryzcy Sep 7, 2022
b751682
Normalize scope before storing in database
harryzcy Sep 7, 2022
17f3183
Fix many integration test issues
harryzcy Sep 11, 2022
f83dc7d
Merge branch 'main' into access-token-scope
harryzcy Sep 11, 2022
85db585
Merge branch 'main' into access-token-scope
harryzcy Oct 1, 2022
fca8b79
Merge branch 'main' into access-token-scope
harryzcy Oct 29, 2022
f3942ef
Revert api router and integration tests
harryzcy Oct 29, 2022
fafc36d
Support specifying scope in reqToken
harryzcy Oct 29, 2022
06e9c81
Support getting scoped token in integration tests
harryzcy Oct 29, 2022
be5164b
Restrict token scope for notifications
harryzcy Oct 29, 2022
9f7db16
Add notification scope to TestEventSourceManagerRun
harryzcy Oct 29, 2022
4464289
Add scope to 'user' api
harryzcy Oct 29, 2022
800de93
Fix gpg key token tests
harryzcy Oct 30, 2022
1fe42fb
Add repo scope to user/applications
harryzcy Oct 30, 2022
32cac39
Fix gpg key token name in web
harryzcy Oct 30, 2022
99f30f6
Add more repo scope to integration tests
harryzcy Oct 30, 2022
f839856
Fix git tests
harryzcy Oct 30, 2022
0583e79
Add gpg key scope to a user test
harryzcy Oct 30, 2022
a282f02
Limit token scope for some repo APIs
harryzcy Oct 30, 2022
0bee969
Add repo scope in TestGPGKeys
harryzcy Oct 30, 2022
182b984
Fix repo scope in TestGPGKeys again
harryzcy Oct 30, 2022
965de53
Fix more repo scope issues
harryzcy Oct 30, 2022
f376275
Fix the token for repo hooks
harryzcy Oct 30, 2022
0a837e7
Include repo scope in TestAPIReposRaw
harryzcy Oct 30, 2022
a44f62e
Add repo scope to TestAPIRepoTeams
harryzcy Oct 30, 2022
755faf6
Fix more integration tests
harryzcy Oct 30, 2022
b4c35e8
Fix typos that causes errors
harryzcy Oct 30, 2022
0ff93b8
Add fixes for delete_repo scope
harryzcy Oct 30, 2022
37c59c9
There are so many fixes
harryzcy Oct 30, 2022
f13904a
Reuse code in `modules/util`
harryzcy Oct 30, 2022
7a3b165
Include copyright statement
harryzcy Oct 30, 2022
62c9f70
Limit repo scope on /api/v1/{user}/{repo}/issues
harryzcy Oct 30, 2022
15ed960
Fix several integration tests
harryzcy Oct 30, 2022
11a5103
Include repo scope in TestMigrateGiteaForm
harryzcy Oct 30, 2022
290b63d
Merge branch 'main' into access-token-scope
harryzcy Oct 30, 2022
d6d6d97
Apply repo scope to more APIs
harryzcy Oct 30, 2022
0a8ab09
Restrict repo scope to remaining repos endpoints
harryzcy Oct 31, 2022
f45bfe3
No token needed for some public info
harryzcy Oct 31, 2022
0f25b04
Cleanup one duplicated reqToken call
harryzcy Oct 31, 2022
f76259b
Add package scope for package APIs
harryzcy Oct 31, 2022
6d7e7e5
Fix package integration tests
harryzcy Oct 31, 2022
9f2af77
Merge branch 'main' into access-token-scope
harryzcy Oct 31, 2022
e8af871
Limit scope for `/api/v1/orgs`
harryzcy Nov 1, 2022
dc6ac14
Fix some test but still have works to do
harryzcy Nov 1, 2022
853eb68
One more fix
harryzcy Nov 1, 2022
45f716f
Fix the scope required for /api/v1/orgs/{org}/teams
harryzcy Nov 1, 2022
8b0bc32
Disallow unauthenticated call to users/{user}/orgs
harryzcy Nov 1, 2022
8f03691
Fix TestAPIGetAll
harryzcy Nov 1, 2022
b6f6ad4
Merge branch 'main' into access-token-scope
harryzcy Nov 2, 2022
2451acc
Add scope to teams APIs
harryzcy Nov 2, 2022
93fbca3
Ensure token exists before running reqTeamMembership
harryzcy Nov 2, 2022
a10b8dc
Add sudo token to /admin API
harryzcy Nov 2, 2022
99f05e8
Support public_repo scope
harryzcy Nov 2, 2022
8e45f81
Introduce database migration
harryzcy Nov 2, 2022
af08a1e
Run make fmt
harryzcy Nov 2, 2022
a48d07d
Merge remote-tracking branch 'upstream/main' into access-token-scope
harryzcy Nov 2, 2022
9a63bb9
Copy and define struct definition in migrations
harryzcy Nov 2, 2022
184cf7c
Merge remote-tracking branch 'upstream/main' into access-token-scope
harryzcy Nov 3, 2022
45d0c22
Update migration description
harryzcy Nov 3, 2022
fa00b5e
Merge remote-tracking branch 'upstream/main' into access-token-scope
harryzcy Nov 3, 2022
32fa981
Fix test error introduced by merge commits
harryzcy Nov 3, 2022
13cd621
List only the modified fields in migration
harryzcy Nov 4, 2022
525c11d
Merge branch 'main' into access-token-scope
6543 Nov 4, 2022
f99cb7f
Restore a test for comments api
harryzcy Nov 5, 2022
7706cb3
Fix linting issues
harryzcy Nov 5, 2022
38da63a
Migrate without default and update old records
harryzcy Nov 5, 2022
bef352a
Update httpsig test
harryzcy Nov 5, 2022
d60b20b
Allow auth via signatures for admin apis
harryzcy Nov 6, 2022
191f3b4
Remove token in TestHTTPSigCert
harryzcy Nov 6, 2022
de66220
Remove unused function
harryzcy Nov 6, 2022
071fe39
Fix failing tests
harryzcy Nov 6, 2022
9123e54
Update integration tests
harryzcy Nov 6, 2022
e412003
Merge branch 'main' into access-token-scope
harryzcy Nov 6, 2022
9b662fe
Update routers/api/v1/api.go
harryzcy Nov 8, 2022
e755747
Add admin:application scope
harryzcy Nov 9, 2022
143c851
Fix unit testing
harryzcy Nov 9, 2022
be84669
Update token scope of `/api/v1/user/applications`
harryzcy Nov 9, 2022
cd1e422
Update comment
harryzcy Nov 9, 2022
28b1326
Update frontend to add the new scope
harryzcy Nov 9, 2022
4f38d44
Fix auth functions to allow either token or sigs
harryzcy Nov 9, 2022
fb9b97f
Merge branch 'main' into access-token-scope
harryzcy Nov 9, 2022
9abd5ab
[doc] Add token scopes doc
harryzcy Nov 11, 2022
0ddab28
Fix `repo:status` token
harryzcy Nov 11, 2022
0883603
Rewrite scope logic to let write:* contain read:*
harryzcy Nov 12, 2022
bd81d43
Merge branch 'main' into access-token-scope
harryzcy Nov 12, 2022
2af8768
Use constant `AccessTokenScopeSudo` in api.go
harryzcy Nov 17, 2022
bb4a64b
Merge branch 'main' into access-token-scope
harryzcy Nov 17, 2022
50a3c5f
Update access token form and update tests to use constants
harryzcy Nov 18, 2022
7c4ecf4
Fix lint issue
harryzcy Nov 18, 2022
dc1a717
Fix TestAPIOrgDeny
harryzcy Nov 18, 2022
633ca9e
Merge branch 'main' into access-token-scope
harryzcy Nov 18, 2022
240656a
Catch missed constants
harryzcy Nov 18, 2022
ed97ed0
Merge branch 'main' into access-token-scope
harryzcy Nov 20, 2022
c648db2
Merge remote-tracking branch 'upstream/main' into access-token-scope
harryzcy Nov 28, 2022
b1ddfea
Document current implementation limitation
harryzcy Nov 30, 2022
826b160
Update copyright style
harryzcy Nov 30, 2022
0e0c4dd
Ensure migration is safe
harryzcy Dec 1, 2022
5238a4d
Merge branch 'main' into access-token-scope
harryzcy Dec 1, 2022
3b93df8
Fix integration tests after merge
harryzcy Dec 1, 2022
16b13a7
Use Forbidden for tokens without required scope
harryzcy Dec 2, 2022
0cecf88
Merge branch 'main' into access-token-scope
harryzcy Dec 2, 2022
5f70625
Fix indentation issue
harryzcy Dec 2, 2022
991e965
Merge branch 'main' into access-token-scope
harryzcy Dec 7, 2022
5f0076c
Fix data race in integration tests
harryzcy Dec 11, 2022
73dff67
Merge branch 'main' into access-token-scope
harryzcy Dec 11, 2022
5fecbd1
Fix License header issue
harryzcy Dec 11, 2022
3c8be2e
Fix tests errors caused by merge
harryzcy Dec 12, 2022
730231c
Merge branch 'main' into access-token-scope
harryzcy Dec 12, 2022
6b89fe7
Merge branch 'main' into access-token-scope
harryzcy Dec 14, 2022
0e4063a
Merge branch 'main' into access-token-scope
harryzcy Dec 16, 2022
4caafb6
Merge branch 'main' into access-token-scope
harryzcy Dec 30, 2022
a7340b8
Prevent `IsApiToken` change
harryzcy Dec 30, 2022
6796f66
Fix integration tests after merging from upstream
harryzcy Dec 30, 2022
21e5063
Merge branch 'main' into access-token-scope
harryzcy Dec 30, 2022
e5d0e33
Merge branch 'main' into access-token-scope
harryzcy Jan 3, 2023
a1b52e4
Document all token scopes
harryzcy Jan 4, 2023
e3114bb
Update docs/content/doc/developers/oauth2-provider.en-us.md
harryzcy Jan 4, 2023
620df8d
Merge branch 'main' into access-token-scope
harryzcy Jan 4, 2023
7beea96
Merge branch 'main' into access-token-scope
harryzcy Jan 7, 2023
c80cc09
Merge branch 'main' into access-token-scope
harryzcy Jan 11, 2023
6ffebec
Merge branch 'main' into access-token-scope
harryzcy Jan 16, 2023
cd836c7
Use map to store token scope bits
harryzcy Jan 16, 2023
70d0c83
Use custom type for all scope variables
harryzcy Jan 16, 2023
bce707f
Fix backend lints
harryzcy Jan 16, 2023
d0a27a6
Update types for bitmap constants as well
harryzcy Jan 16, 2023
64d5251
Fix typing issue in integration tests
harryzcy Jan 16, 2023
32fa0aa
Fix type in router APIs
harryzcy Jan 16, 2023
0d9160b
Avoid unnecessary type conversion
harryzcy Jan 16, 2023
7d7344f
More unnecessary conversion
harryzcy Jan 16, 2023
6f2974a
Include sudo scope for old tokens during migration
harryzcy Jan 16, 2023
6275a9d
Merge branch 'main' into access-token-scope
harryzcy Jan 16, 2023
1f596e7
Use string type in migration
harryzcy Jan 17, 2023
3a3ef8f
Merge branch 'main' into access-token-scope
lunny Jan 17, 2023
49aba97
Merge branch 'main' into access-token-scope
jolheiser Jan 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion docs/content/doc/developers/oauth2-provider.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,41 @@ To use the Authorization Code Grant as a third party application it is required

## Scopes

Currently Gitea does not support scopes (see [#4300](https://github.com/go-gitea/gitea/issues/4300)) and all third party applications will be granted access to all resources of the user and their organizations.
Gitea supports the following scopes for tokens:

| Name | Description |
| ---- | ----------- |
| **(no scope)** | Grants read-only access to public user profile and public repositories. |
| **repo** | Full control over all repositories. |
|     **repo:status** | Grants read/write access to commit status in all repositories. |
harryzcy marked this conversation as resolved.
Show resolved Hide resolved
|     **public_repo** | Grants read/write access to public repositories only. |
| **admin:repo_hook** | Grants access to repository hooks of all repositories. This is included in the `repo` scope. |
|     **write:repo_hook** | |
|     **read:repo_hook** | |
| **admin:org** | |
|     **write:org** | |
|     **read:org** | |
| **admin:public_key** | |
|     **write:public_key** | |
|     **read:public_key** | |
| **admin:org_hook** | |
| **notification** | |
| **user** | |
|     **read:user** | |
|     **user:email** | |
|     **user:follow** | |
| **delete_repo** | |
| **package** | |
|     **write:package** | |
|     **read:package** | |
|     **delete:package** | |
| **admin:gpg_key** | |
|     **write:gpg_key** | |
|     **read:gpg_key** | |
| **admin:application** | |
|     **write:application** | |
|     **read:application** | |
| **sudo** | Allows to perform actions as the site admin. |

## Client types

Expand Down
1 change: 1 addition & 0 deletions models/auth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ type AccessToken struct {
TokenHash string `xorm:"UNIQUE"` // sha256 of token
TokenSalt string
TokenLastEight string `xorm:"INDEX token_last_eight"`
Scope AccessTokenScope

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Expand Down
237 changes: 237 additions & 0 deletions models/auth/token_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package auth

import (
"fmt"
"strings"

"code.gitea.io/gitea/modules/util"
)

// AccessTokenScope represents the scope for an access token.
type AccessTokenScope string

const (
AccessTokenScopeAll = "all"
harryzcy marked this conversation as resolved.
Show resolved Hide resolved

AccessTokenScopeRepo = "repo"
AccessTokenScopeRepoStatus = "repo:status"
AccessTokenScopePublicRepo = "public_repo"

AccessTokenScopeAdminOrg = "admin:org"
AccessTokenScopeWriteOrg = "write:org"
AccessTokenScopeReadOrg = "read:org"

AccessTokenScopeAdminPublicKey = "admin:public_key"
AccessTokenScopeWritePublicKey = "write:public_key"
AccessTokenScopeReadPublicKey = "read:public_key"

AccessTokenScopeAdminRepoHook = "admin:repo_hook"
AccessTokenScopeWriteRepoHook = "write:repo_hook"
AccessTokenScopeReadRepoHook = "read:repo_hook"

AccessTokenScopeAdminOrgHook = "admin:org_hook"

AccessTokenScopeNotification = "notification"

AccessTokenScopeUser = "user"
AccessTokenScopeReadUser = "read:user"
AccessTokenScopeUserEmail = "user:email"
AccessTokenScopeUserFollow = "user:follow"

AccessTokenScopeDeleteRepo = "delete_repo"

AccessTokenScopePackage = "package"
AccessTokenScopeWritePackage = "write:package"
AccessTokenScopeReadPackage = "read:package"
AccessTokenScopeDeletePackage = "delete:package"

AccessTokenScopeAdminGPGKey = "admin:gpg_key"
AccessTokenScopeWriteGPGKey = "write:gpg_key"
AccessTokenScopeReadGPGKey = "read:gpg_key"

AccessTokenScopeAdminApplication = "admin:application"
AccessTokenScopeWriteApplication = "write:application"
AccessTokenScopeReadApplication = "read:application"

AccessTokenScopeSudo = "sudo"
)

// AccessTokenScopeBitmap represents a bitmap of access token scopes.
type AccessTokenScopeBitmap uint64
harryzcy marked this conversation as resolved.
Show resolved Hide resolved

// Bitmap of each scope, including the child scopes.
const (
// AccessTokenScopeAllBits is the bitmap of all access token scopes.
AccessTokenScopeAllBits = AccessTokenScopeRepoBits |
AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits |
AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits |
AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits
harryzcy marked this conversation as resolved.
Show resolved Hide resolved

AccessTokenScopeRepoBits = 1<<iota | AccessTokenScopeRepoStatusBits | AccessTokenScopePublicRepoBits | AccessTokenScopeAdminRepoHookBits
AccessTokenScopeRepoStatusBits = 1 << iota
AccessTokenScopePublicRepoBits = 1 << iota

AccessTokenScopeAdminOrgBits = 1<<iota | AccessTokenScopeWriteOrgBits
AccessTokenScopeWriteOrgBits = 1<<iota | AccessTokenScopeReadOrgBits
AccessTokenScopeReadOrgBits = 1 << iota

AccessTokenScopeAdminPublicKeyBits = 1<<iota | AccessTokenScopeWritePublicKeyBits
AccessTokenScopeWritePublicKeyBits = 1<<iota | AccessTokenScopeReadPublicKeyBits
AccessTokenScopeReadPublicKeyBits = 1 << iota

AccessTokenScopeAdminRepoHookBits = 1<<iota | AccessTokenScopeWriteRepoHookBits
AccessTokenScopeWriteRepoHookBits = 1<<iota | AccessTokenScopeReadRepoHookBits
AccessTokenScopeReadRepoHookBits = 1 << iota

AccessTokenScopeAdminOrgHookBits = 1 << iota

AccessTokenScopeNotificationBits = 1 << iota

AccessTokenScopeUserBits = 1<<iota | AccessTokenScopeReadUserBits | AccessTokenScopeUserEmailBits | AccessTokenScopeUserFollowBits
AccessTokenScopeReadUserBits = 1 << iota
AccessTokenScopeUserEmailBits = 1 << iota
AccessTokenScopeUserFollowBits = 1 << iota

AccessTokenScopeDeleteRepoBits = 1 << iota

AccessTokenScopePackageBits = 1<<iota | AccessTokenScopeWritePackageBits | AccessTokenScopeDeletePackageBits
AccessTokenScopeWritePackageBits = 1<<iota | AccessTokenScopeReadPackageBits
AccessTokenScopeReadPackageBits = 1 << iota
AccessTokenScopeDeletePackageBits = 1 << iota

AccessTokenScopeAdminGPGKeyBits = 1<<iota | AccessTokenScopeWriteGPGKeyBits
AccessTokenScopeWriteGPGKeyBits = 1<<iota | AccessTokenScopeReadGPGKeyBits
AccessTokenScopeReadGPGKeyBits = 1 << iota

AccessTokenScopeAdminApplicationBits = 1<<iota | AccessTokenScopeWriteApplicationBits
AccessTokenScopeWriteApplicationBits = 1<<iota | AccessTokenScopeReadApplicationBits
AccessTokenScopeReadApplicationBits = 1 << iota

AccessTokenScopeSudoBits = 1 << iota

// 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 precedes child scopes.
var AllAccessTokenScopes = []string{
harryzcy marked this conversation as resolved.
Show resolved Hide resolved
AccessTokenScopeRepo, AccessTokenScopeRepoStatus, AccessTokenScopePublicRepo,
AccessTokenScopeAdminOrg, AccessTokenScopeWriteOrg, AccessTokenScopeReadOrg,
AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey,
AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook,
AccessTokenScopeAdminOrgHook,
AccessTokenScopeNotification,
AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow,
AccessTokenScopeDeleteRepo,
AccessTokenScopePackage, AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, AccessTokenScopeDeletePackage,
AccessTokenScopeAdminGPGKey, AccessTokenScopeWriteGPGKey, AccessTokenScopeReadGPGKey,
AccessTokenScopeAdminApplication, AccessTokenScopeWriteApplication, AccessTokenScopeReadApplication,
AccessTokenScopeSudo,
}

// AllAccessTokenScopeBits contains all access token scopes.
// The order must be the same as AllAccessTokenScopeBits.
var AllAccessTokenScopeBits = []AccessTokenScopeBitmap{
harryzcy marked this conversation as resolved.
Show resolved Hide resolved
AccessTokenScopeRepoBits, AccessTokenScopeRepoStatusBits, AccessTokenScopePublicRepoBits,
AccessTokenScopeAdminOrgBits, AccessTokenScopeWriteOrgBits, AccessTokenScopeReadOrgBits,
AccessTokenScopeAdminPublicKeyBits, AccessTokenScopeWritePublicKeyBits, AccessTokenScopeReadPublicKeyBits,
AccessTokenScopeAdminRepoHookBits, AccessTokenScopeWriteRepoHookBits, AccessTokenScopeReadRepoHookBits,
AccessTokenScopeAdminOrgHookBits,
AccessTokenScopeNotificationBits,
AccessTokenScopeUserBits, AccessTokenScopeReadUserBits, AccessTokenScopeUserEmailBits, AccessTokenScopeUserFollowBits,
AccessTokenScopeDeleteRepoBits,
AccessTokenScopePackageBits, AccessTokenScopeWritePackageBits, AccessTokenScopeReadPackageBits, AccessTokenScopeDeletePackageBits,
AccessTokenScopeAdminGPGKeyBits, AccessTokenScopeWriteGPGKeyBits, AccessTokenScopeReadGPGKeyBits,
AccessTokenScopeAdminApplicationBits, AccessTokenScopeWriteApplicationBits, AccessTokenScopeReadApplicationBits,
AccessTokenScopeSudoBits,
}

// Parse parses the scope string into a bitmap, thus removing possible duplicates.
func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
list := strings.Split(string(s), ",")

var bitmap AccessTokenScopeBitmap
for _, v := range list {
if v == "" {
continue
}
if v == AccessTokenScopeAll {
bitmap |= AccessTokenScopeAllBits
continue
}

idx := util.FindStringInSlice(v, AllAccessTokenScopes)
if idx < 0 {
return 0, fmt.Errorf("invalid access token scope: %s", v)
}
bitmap |= AllAccessTokenScopeBits[idx]
}
return bitmap, nil
}

// 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
}

// HasScope returns true if the string has the given scope
func (s AccessTokenScope) HasScope(scope string) (bool, error) {
bitmap, err := s.Parse()
if err != nil {
return false, err
}

return bitmap.HasScope(scope)
}

// HasScope returns true if the string has the given scope
func (bitmap AccessTokenScopeBitmap) HasScope(scope string) (bool, error) {
index := util.FindStringInSlice(scope, AllAccessTokenScopes)
if index == -1 {
return false, fmt.Errorf("invalid access token scope: %s", scope)
}

expectedBitmap := AllAccessTokenScopeBits[index]

return bitmap&expectedBitmap == expectedBitmap, 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 i, v := range AllAccessTokenScopes {
// no need for error checking here, since we know the scope is valid
if ok, _ := bitmap.HasScope(v); ok {
current := reconstruct | AllAccessTokenScopeBits[i]
if current == reconstruct {
continue
}

reconstruct = current
scopes = append(scopes, v)
}
}

scope := AccessTokenScope(strings.Join(scopes, ","))
scope = AccessTokenScope(strings.ReplaceAll(
string(scope),
"repo,admin:org,admin:public_key,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application",
"all",
))
return scope
}
84 changes: 84 additions & 0 deletions models/auth/token_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package auth

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAccessTokenScope_Normalize(t *testing.T) {
tests := []struct {
in AccessTokenScope
out AccessTokenScope
err error
}{
{"", "", nil},
{"repo", "repo", nil},
{"repo,repo:status", "repo", nil},
{"repo,public_repo", "repo", nil},
{"admin:public_key,write:public_key", "admin:public_key", nil},
{"admin:public_key,read:public_key", "admin:public_key", nil},
{"write:public_key,read:public_key", "write:public_key", nil}, // read is include in write
{"admin:repo_hook,write:repo_hook", "admin:repo_hook", nil},
{"admin:repo_hook,read:repo_hook", "admin:repo_hook", nil},
{"repo,admin:repo_hook,read:repo_hook", "repo", nil}, // admin:repo_hook is a child scope of repo
{"repo,read:repo_hook", "repo", nil}, // read:repo_hook is a child scope of repo
{"user", "user", nil},
{"user,read:user", "user", nil},
{"user,admin:org,write:org", "admin:org,user", nil},
{"admin:org,write:org,user", "admin:org,user", nil},
{"package", "package", nil},
{"package,write:package", "package", nil},
{"package,write:package,delete:package", "package", nil},
{"write:package,read:package", "write:package", nil}, // read is include in write
{"write:package,delete:package", "write:package,delete:package", nil}, // write and delete are not include in each other
{"admin:gpg_key", "admin:gpg_key", nil},
{"admin:gpg_key,write:gpg_key", "admin:gpg_key", nil},
{"admin:gpg_key,write:gpg_key,user", "user,admin:gpg_key", nil},
{"admin:application,write:application,user", "user,admin:application", nil},
{"all", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application", "all", nil},
{"repo,admin:org,admin:public_key,admin:repo_hook,admin:org_hook,notification,user,delete_repo,package,admin:gpg_key,admin:application,sudo", "all,sudo", nil},
}

for _, test := range tests {
t.Run(string(test.in), func(t *testing.T) {
scope, err := test.in.Normalize()
assert.Equal(t, test.out, scope)
assert.Equal(t, test.err, err)
})
}
}

func TestAccessTokenScope_HasScope(t *testing.T) {
tests := []struct {
in AccessTokenScope
scope string
out bool
err error
}{
{"repo", "repo", true, nil},
{"repo", "repo:status", true, nil},
{"repo", "public_repo", true, nil},
{"repo", "admin:org", false, nil},
{"repo", "admin:public_key", false, nil},
{"repo:status", "repo", false, nil},
{"repo:status", "public_repo", false, nil},
{"admin:org", "write:org", true, nil},
{"admin:org", "read:org", true, nil},
{"admin:org", "admin:org", true, nil},
{"user", "read:user", true, nil},
{"package", "write:package", true, nil},
}

for _, test := range tests {
t.Run(string(test.in), func(t *testing.T) {
scope, err := test.in.HasScope(test.scope)
assert.Equal(t, test.out, scope)
assert.Equal(t, test.err, err)
})
}
}
2 changes: 2 additions & 0 deletions models/migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,8 @@ var migrations = []Migration{
NewMigration("Create secrets table", v1_19.CreateSecretsTable),
// v237 -> v238
NewMigration("Drop ForeignReference table", v1_19.DropForeignReferenceTable),
// v238 -> v239
NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens),
}

// GetCurrentDBVersion returns the current db version
Expand Down
Loading