Skip to content

Commit 89d1140

Browse files
committed
Add token scope helper functions
1 parent 90e3b58 commit 89d1140

File tree

3 files changed

+176
-1
lines changed

3 files changed

+176
-1
lines changed

models/auth/token.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ type AccessToken struct {
5959
TokenHash string `xorm:"UNIQUE"` // sha256 of token
6060
TokenSalt string
6161
TokenLastEight string `xorm:"token_last_eight"`
62-
Scope string
62+
Scope AccessTokenScope
6363

6464
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
6565
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`

models/token_scope.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package models
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
type AccessTokenScope string
9+
10+
const (
11+
AccessTokenScopeRepo = "repo"
12+
AccessTokenScopeRepoStatus = "repo:status"
13+
AccessTokenScopePublicRepo = "public_repo"
14+
15+
AccessTokenScopeAdminOrg = "admin:org"
16+
AccessTokenScopeWriteOrg = "write:org"
17+
AccessTokenScopeReadOrg = "read:org"
18+
19+
AccessTokenScopeAdminPublicKey = "admin:public_key"
20+
AccessTokenScopeWritePublicKey = "write:public_key"
21+
AccessTokenScopeReadPublicKey = "read:public_key"
22+
23+
AccessTokenScopeAdminRepoHook = "admin:repo_hook"
24+
AccessTokenScopeWriteRepoHook = "write:repo_hook"
25+
AccessTokenScopeReadRepoHook = "read:repo_hook"
26+
27+
AccessTokenScopeAdminOrgHook = "admin:org_hook"
28+
29+
AccessTokenScopeNotification = "notification"
30+
31+
AccessTokenScopeUser = "user"
32+
AccessTokenScopeReadUser = "read:user"
33+
AccessTokenScopeUserEmail = "user:email"
34+
AccessTokenScopeUserFollow = "user:follow"
35+
36+
AccessTokenScopeDeleteRepo = "delete_repo"
37+
38+
AccessTokenScopePackage = "package"
39+
AccessTokenScopeWritePackage = "write:package"
40+
AccessTokenScopeReadPackage = "read:package"
41+
AccessTokenScopeDeletePackage = "delete:package"
42+
43+
AccessTokenScopeAdminGPGKey = "admin:gpg_key"
44+
AccessTokenScopeWriteGPGKey = "write:gpg_key"
45+
AccessTokenScopeReadGPGKey = "read:gpg_key"
46+
)
47+
48+
// AllAccessTokenScopes contains all access token scopes.
49+
// The order is important: parent scope must precedes child scopes.
50+
var AllAccessTokenScopes = []string{
51+
AccessTokenScopeRepo, AccessTokenScopeRepoStatus, AccessTokenScopePublicRepo,
52+
AccessTokenScopeAdminOrg, AccessTokenScopeWriteOrg, AccessTokenScopeReadOrg,
53+
AccessTokenScopeAdminPublicKey, AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey,
54+
AccessTokenScopeAdminRepoHook, AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook,
55+
AccessTokenScopeAdminOrgHook,
56+
AccessTokenScopeNotification,
57+
AccessTokenScopeUser, AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow,
58+
AccessTokenScopeDeleteRepo,
59+
AccessTokenScopePackage, AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, AccessTokenScopeDeletePackage,
60+
AccessTokenScopeAdminGPGKey, AccessTokenScopeWriteGPGKey, AccessTokenScopeReadGPGKey,
61+
}
62+
63+
func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
64+
list := strings.Split(string(s), ",")
65+
66+
var bitmap AccessTokenScopeBitmap
67+
for _, v := range list {
68+
if v == "" {
69+
continue
70+
}
71+
72+
idx := sliceIndex(AllAccessTokenScopes, v)
73+
if idx < 0 {
74+
return 0, fmt.Errorf("invalid access token scope: %s", v)
75+
}
76+
bitmap |= 1 << uint(idx)
77+
}
78+
return bitmap, nil
79+
}
80+
81+
func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
82+
bitmap, err := s.Parse()
83+
if err != nil {
84+
return "", err
85+
}
86+
87+
return bitmap.ToScope(), nil
88+
}
89+
90+
type AccessTokenScopeBitmap uint64
91+
92+
func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope {
93+
var scopes []string
94+
95+
groupedScope := make(map[string]struct{})
96+
for i, v := range AllAccessTokenScopes {
97+
if bitmap&(1<<uint(i)) != 0 {
98+
switch v {
99+
// Parse scopes that contains multiple sub-scopes
100+
case AccessTokenScopeRepo, AccessTokenScopeAdminOrg, AccessTokenScopeAdminPublicKey,
101+
AccessTokenScopeAdminRepoHook, AccessTokenScopeUser, AccessTokenScopePackage, AccessTokenScopeAdminGPGKey:
102+
groupedScope[v] = struct{}{}
103+
104+
// If parent scope is set, all sub-scopes shouldn't be added
105+
case AccessTokenScopeRepoStatus, AccessTokenScopePublicRepo:
106+
if _, ok := groupedScope[AccessTokenScopeRepo]; ok {
107+
continue
108+
}
109+
case AccessTokenScopeWriteOrg, AccessTokenScopeReadOrg:
110+
if _, ok := groupedScope[AccessTokenScopeAdminOrg]; ok {
111+
continue
112+
}
113+
case AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey:
114+
if _, ok := groupedScope[AccessTokenScopeAdminPublicKey]; ok {
115+
continue
116+
}
117+
case AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook:
118+
if _, ok := groupedScope[AccessTokenScopeAdminRepoHook]; ok {
119+
continue
120+
}
121+
case AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow:
122+
if _, ok := groupedScope[AccessTokenScopeUser]; ok {
123+
continue
124+
}
125+
case AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, AccessTokenScopeDeletePackage:
126+
if _, ok := groupedScope[AccessTokenScopePackage]; ok {
127+
continue
128+
}
129+
case AccessTokenScopeWriteGPGKey, AccessTokenScopeReadGPGKey:
130+
if _, ok := groupedScope[AccessTokenScopeAdminGPGKey]; ok {
131+
continue
132+
}
133+
}
134+
scopes = append(scopes, v)
135+
}
136+
}
137+
138+
return AccessTokenScope(strings.Join(scopes, ","))
139+
}
140+
141+
func sliceIndex(slice []string, element string) int {
142+
for i, v := range slice {
143+
if v == element {
144+
return i
145+
}
146+
}
147+
return -1
148+
}

models/token_scope_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package models
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestAccessTokenScope_Normalize(t *testing.T) {
10+
tests := []struct {
11+
in AccessTokenScope
12+
out AccessTokenScope
13+
err error
14+
}{
15+
{"", "", nil},
16+
{"user", "user", nil},
17+
{"user,read:user", "user", nil},
18+
}
19+
20+
for _, test := range tests {
21+
t.Run(string(test.in), func(t *testing.T) {
22+
scope, err := test.in.Normalize()
23+
assert.Equal(t, test.out, scope)
24+
assert.Equal(t, test.err, err)
25+
})
26+
}
27+
}

0 commit comments

Comments
 (0)