Skip to content

Commit

Permalink
Add token scope helper functions
Browse files Browse the repository at this point in the history
  • Loading branch information
harryzcy committed Sep 6, 2022
1 parent 90e3b58 commit 89d1140
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 1 deletion.
2 changes: 1 addition & 1 deletion models/auth/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ type AccessToken struct {
TokenHash string `xorm:"UNIQUE"` // sha256 of token
TokenSalt string
TokenLastEight string `xorm:"token_last_eight"`
Scope string
Scope AccessTokenScope

CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
Expand Down
148 changes: 148 additions & 0 deletions models/token_scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package models

import (
"fmt"
"strings"
)

type AccessTokenScope string

const (
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"
)

// AllAccessTokenScopes contains all access token scopes.
// The order is important: parent scope must precedes child scopes.
var AllAccessTokenScopes = []string{
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,
}

func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
list := strings.Split(string(s), ",")

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

idx := sliceIndex(AllAccessTokenScopes, v)
if idx < 0 {
return 0, fmt.Errorf("invalid access token scope: %s", v)
}
bitmap |= 1 << uint(idx)
}
return bitmap, nil
}

func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
bitmap, err := s.Parse()
if err != nil {
return "", err
}

return bitmap.ToScope(), nil
}

type AccessTokenScopeBitmap uint64

func (bitmap AccessTokenScopeBitmap) ToScope() AccessTokenScope {
var scopes []string

groupedScope := make(map[string]struct{})
for i, v := range AllAccessTokenScopes {
if bitmap&(1<<uint(i)) != 0 {
switch v {
// Parse scopes that contains multiple sub-scopes
case AccessTokenScopeRepo, AccessTokenScopeAdminOrg, AccessTokenScopeAdminPublicKey,
AccessTokenScopeAdminRepoHook, AccessTokenScopeUser, AccessTokenScopePackage, AccessTokenScopeAdminGPGKey:
groupedScope[v] = struct{}{}

// If parent scope is set, all sub-scopes shouldn't be added
case AccessTokenScopeRepoStatus, AccessTokenScopePublicRepo:
if _, ok := groupedScope[AccessTokenScopeRepo]; ok {
continue
}
case AccessTokenScopeWriteOrg, AccessTokenScopeReadOrg:
if _, ok := groupedScope[AccessTokenScopeAdminOrg]; ok {
continue
}
case AccessTokenScopeWritePublicKey, AccessTokenScopeReadPublicKey:
if _, ok := groupedScope[AccessTokenScopeAdminPublicKey]; ok {
continue
}
case AccessTokenScopeWriteRepoHook, AccessTokenScopeReadRepoHook:
if _, ok := groupedScope[AccessTokenScopeAdminRepoHook]; ok {
continue
}
case AccessTokenScopeReadUser, AccessTokenScopeUserEmail, AccessTokenScopeUserFollow:
if _, ok := groupedScope[AccessTokenScopeUser]; ok {
continue
}
case AccessTokenScopeWritePackage, AccessTokenScopeReadPackage, AccessTokenScopeDeletePackage:
if _, ok := groupedScope[AccessTokenScopePackage]; ok {
continue
}
case AccessTokenScopeWriteGPGKey, AccessTokenScopeReadGPGKey:
if _, ok := groupedScope[AccessTokenScopeAdminGPGKey]; ok {
continue
}
}
scopes = append(scopes, v)
}
}

return AccessTokenScope(strings.Join(scopes, ","))
}

func sliceIndex(slice []string, element string) int {
for i, v := range slice {
if v == element {
return i
}
}
return -1
}
27 changes: 27 additions & 0 deletions models/token_scope_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package models

import (
"testing"

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

func TestAccessTokenScope_Normalize(t *testing.T) {
tests := []struct {
in AccessTokenScope
out AccessTokenScope
err error
}{
{"", "", nil},
{"user", "user", nil},
{"user,read:user", "user", 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)
})
}
}

0 comments on commit 89d1140

Please sign in to comment.