aboutsummaryrefslogtreecommitdiffstats
path: root/models/auth/token_scope.go
blob: 38733a1c8f30549a00c68046be1b4f4beb8174c0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package auth

import (
	"fmt"
	"strings"
)

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

const (
	AccessTokenScopeAll AccessTokenScope = "all"

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

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

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

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

	AccessTokenScopeAdminOrgHook AccessTokenScope = "admin:org_hook"

	AccessTokenScopeNotification AccessTokenScope = "notification"

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

	AccessTokenScopeDeleteRepo AccessTokenScope = "delete_repo"

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

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

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

	AccessTokenScopeSudo AccessTokenScope = "sudo"
)

// AccessTokenScopeBitmap represents a bitmap of access token scopes.
type AccessTokenScopeBitmap uint64

// Bitmap of each scope, including the child scopes.
const (
	// AccessTokenScopeAllBits is the bitmap of all access token scopes, except `sudo`.
	AccessTokenScopeAllBits AccessTokenScopeBitmap = AccessTokenScopeRepoBits |
		AccessTokenScopeAdminOrgBits | AccessTokenScopeAdminPublicKeyBits | AccessTokenScopeAdminOrgHookBits |
		AccessTokenScopeNotificationBits | AccessTokenScopeUserBits | AccessTokenScopeDeleteRepoBits |
		AccessTokenScopePackageBits | AccessTokenScopeAdminGPGKeyBits | AccessTokenScopeAdminApplicationBits

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

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

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

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

	AccessTokenScopeAdminOrgHookBits AccessTokenScopeBitmap = 1 << iota

	AccessTokenScopeNotificationBits AccessTokenScopeBitmap = 1 << iota

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

	AccessTokenScopeDeleteRepoBits AccessTokenScopeBitmap = 1 << iota

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

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

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

	AccessTokenScopeSudoBits AccessTokenScopeBitmap = 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 = []AccessTokenScope{
	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.
var allAccessTokenScopeBits = map[AccessTokenScope]AccessTokenScopeBitmap{
	AccessTokenScopeRepo:             AccessTokenScopeRepoBits,
	AccessTokenScopeRepoStatus:       AccessTokenScopeRepoStatusBits,
	AccessTokenScopePublicRepo:       AccessTokenScopePublicRepoBits,
	AccessTokenScopeAdminOrg:         AccessTokenScopeAdminOrgBits,
	AccessTokenScopeWriteOrg:         AccessTokenScopeWriteOrgBits,
	AccessTokenScopeReadOrg:          AccessTokenScopeReadOrgBits,
	AccessTokenScopeAdminPublicKey:   AccessTokenScopeAdminPublicKeyBits,
	AccessTokenScopeWritePublicKey:   AccessTokenScopeWritePublicKeyBits,
	AccessTokenScopeReadPublicKey:    AccessTokenScopeReadPublicKeyBits,
	AccessTokenScopeAdminRepoHook:    AccessTokenScopeAdminRepoHookBits,
	AccessTokenScopeWriteRepoHook:    AccessTokenScopeWriteRepoHookBits,
	AccessTokenScopeReadRepoHook:     AccessTokenScopeReadRepoHookBits,
	AccessTokenScopeAdminOrgHook:     AccessTokenScopeAdminOrgHookBits,
	AccessTokenScopeNotification:     AccessTokenScopeNotificationBits,
	AccessTokenScopeUser:             AccessTokenScopeUserBits,
	AccessTokenScopeReadUser:         AccessTokenScopeReadUserBits,
	AccessTokenScopeUserEmail:        AccessTokenScopeUserEmailBits,
	AccessTokenScopeUserFollow:       AccessTokenScopeUserFollowBits,
	AccessTokenScopeDeleteRepo:       AccessTokenScopeDeleteRepoBits,
	AccessTokenScopePackage:          AccessTokenScopePackageBits,
	AccessTokenScopeWritePackage:     AccessTokenScopeWritePackageBits,
	AccessTokenScopeReadPackage:      AccessTokenScopeReadPackageBits,
	AccessTokenScopeDeletePackage:    AccessTokenScopeDeletePackageBits,
	AccessTokenScopeAdminGPGKey:      AccessTokenScopeAdminGPGKeyBits,
	AccessTokenScopeWriteGPGKey:      AccessTokenScopeWriteGPGKeyBits,
	AccessTokenScopeReadGPGKey:       AccessTokenScopeReadGPGKeyBits,
	AccessTokenScopeAdminApplication: AccessTokenScopeAdminApplicationBits,
	AccessTokenScopeWriteApplication: AccessTokenScopeWriteApplicationBits,
	AccessTokenScopeReadApplication:  AccessTokenScopeReadApplicationBits,
	AccessTokenScopeSudo:             AccessTokenScopeSudoBits,
}

// Parse parses the scope string into a bitmap, thus removing possible duplicates.
func (s AccessTokenScope) Parse() (AccessTokenScopeBitmap, error) {
	var bitmap AccessTokenScopeBitmap

	// The following is the more performant equivalent of 'for _, v := range strings.Split(remainingScope, ",")' as this is hot code
	remainingScopes := string(s)
	for len(remainingScopes) > 0 {
		i := strings.IndexByte(remainingScopes, ',')
		var v string
		if i < 0 {
			v = remainingScopes
			remainingScopes = ""
		} else if i+1 >= len(remainingScopes) {
			v = remainingScopes[:i]
			remainingScopes = ""
		} else {
			v = remainingScopes[:i]
			remainingScopes = remainingScopes[i+1:]
		}
		singleScope := AccessTokenScope(v)
		if singleScope == "" {
			continue
		}
		if singleScope == AccessTokenScopeAll {
			bitmap |= AccessTokenScopeAllBits
			continue
		}

		bits, ok := allAccessTokenScopeBits[singleScope]
		if !ok {
			return 0, fmt.Errorf("invalid access token scope: %s", singleScope)
		}
		bitmap |= bits
	}

	return bitmap, nil
}

// StringSlice returns the AccessTokenScope as a []string
func (s AccessTokenScope) StringSlice() []string {
	return strings.Split(string(s), ",")
}

// 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 AccessTokenScope) (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 AccessTokenScope) (bool, error) {
	expectedBits, ok := allAccessTokenScopeBits[scope]
	if !ok {
		return false, fmt.Errorf("invalid access token scope: %s", scope)
	}

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

			reconstruct = current
			scopes = append(scopes, string(singleScope))
		}
	}

	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
}