fix: validate "scope" for OAuth2, "state" for OpenID (#39642)

1. Fixes: https://github.com/go-gitea/gitea/issues/39641
* The OAuth2 authorize/grant endpoint accepted and persisted arbitrary
scope strings without validation, unlike personal access token creation
which rejects unknown scopes. Normalize and validate form.Scope before
creating or updating a grant, ignore invalid scopes
* `// TODO: at the moment invalid tokens are treated as "all" to avoid
breaking 1.22 behavior` is fixed.
2. To avoid CSRF attack for OpenID endpoint, add a state check

---------

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Shivansh Gargandwxiaoguang authored and GitHub committed 2026-10-12 20:57:41 +00:00
1 parent 4b5f1f46de
commit f270534e83
9 files changed
+257 -57

No files matched your search

+7 -8
View File
@@ -54,7 +54,8 @@ type AccessTokenScope string
// for all categories, write implies read
const (
AccessTokenScopeAll AccessTokenScope = "all"
AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
AccessTokenScopeNone AccessTokenScope = "no-token-scope" // no token permission scopes, so no API permissions
AccessTokenScopePublicOnly AccessTokenScope = "public-only" // limited to public orgs/repos
AccessTokenScopeReadActivityPub AccessTokenScope = "read:activitypub"
AccessTokenScopeWriteActivityPub AccessTokenScope = "write:activitypub"
@@ -147,6 +148,7 @@ var allAccessTokenScopes = []AccessTokenScope{
// allAccessTokenScopeBits contains all access token scopes.
var allAccessTokenScopeBits = map[AccessTokenScope]accessTokenScopeBitmap{
AccessTokenScopeAll: accessTokenScopeAllBits,
AccessTokenScopeNone: 0,
AccessTokenScopePublicOnly: accessTokenScopePublicOnlyBits,
AccessTokenScopeReadActivityPub: accessTokenScopeReadActivityPubBits,
AccessTokenScopeWriteActivityPub: accessTokenScopeWriteActivityPubBits,
@@ -211,11 +213,6 @@ func GetRequiredScopes(level AccessTokenScopeLevel, scopeCategories ...AccessTok
return scopes
}
// ContainsCategory checks if a list of categories contains a specific category
func ContainsCategory(categories []AccessTokenScopeCategory, category AccessTokenScopeCategory) bool {
return slices.Contains(categories, category)
}
// GetScopeLevelFromAccessMode converts permission access mode to scope level
func GetScopeLevelFromAccessMode(mode perm.AccessMode) AccessTokenScopeLevel {
switch mode {
@@ -286,12 +283,14 @@ func (s AccessTokenScope) Normalize() (AccessTokenScope, error) {
if err != nil {
return "", err
}
if bitmap == 0 && slices.Contains(s.StringSlice(), string(AccessTokenScopeNone)) {
return AccessTokenScopeNone, nil
}
return bitmap.toScope(), nil
}
func (s AccessTokenScope) HasPermissionScope() bool {
return s != "" && s != AccessTokenScopePublicOnly
return s != "" && s != AccessTokenScopeNone && s != AccessTokenScopePublicOnly
}
// PublicOnly checks if this token scope is limited to public resources