mirror of
https://github.com/go-gitea/gitea.git
synced 2024-12-04 14:46:57 -05:00
Improve oauth2 scope token handling (#32633)
This commit is contained in:
parent
25cacaf0aa
commit
9ed768adc4
@ -321,7 +321,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !allow {
|
if !allow {
|
||||||
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
|
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s), required=%v, token scope=%v", requiredScopes, scope))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,26 +74,32 @@ type AccessTokenResponse struct {
|
|||||||
// GrantAdditionalScopes returns valid scopes coming from grant
|
// GrantAdditionalScopes returns valid scopes coming from grant
|
||||||
func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
|
func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
|
||||||
// scopes_supported from templates/user/auth/oidc_wellknown.tmpl
|
// scopes_supported from templates/user/auth/oidc_wellknown.tmpl
|
||||||
scopesSupported := []string{
|
generalScopesSupported := []string{
|
||||||
"openid",
|
"openid",
|
||||||
"profile",
|
"profile",
|
||||||
"email",
|
"email",
|
||||||
"groups",
|
"groups",
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenScopes []string
|
var accessScopes []string // the scopes for access control, but not for general information
|
||||||
for _, tokenScope := range strings.Split(grantScopes, " ") {
|
for _, scope := range strings.Split(grantScopes, " ") {
|
||||||
if slices.Index(scopesSupported, tokenScope) == -1 {
|
if scope != "" && !slices.Contains(generalScopesSupported, scope) {
|
||||||
tokenScopes = append(tokenScopes, tokenScope)
|
accessScopes = append(accessScopes, scope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// since version 1.22, access tokens grant full access to the API
|
// since version 1.22, access tokens grant full access to the API
|
||||||
// with this access is reduced only if additional scopes are provided
|
// with this access is reduced only if additional scopes are provided
|
||||||
accessTokenScope := auth.AccessTokenScope(strings.Join(tokenScopes, ","))
|
if len(accessScopes) > 0 {
|
||||||
if accessTokenWithAdditionalScopes, err := accessTokenScope.Normalize(); err == nil && len(tokenScopes) > 0 {
|
accessTokenScope := auth.AccessTokenScope(strings.Join(accessScopes, ","))
|
||||||
return accessTokenWithAdditionalScopes
|
if normalizedAccessTokenScope, err := accessTokenScope.Normalize(); err == nil {
|
||||||
|
return normalizedAccessTokenScope
|
||||||
|
}
|
||||||
|
// TODO: if there are invalid access scopes (err != nil),
|
||||||
|
// then it is treated as "all", maybe in the future we should make it stricter to return an error
|
||||||
|
// at the moment, to avoid breaking 1.22 behavior, invalid tokens are also treated as "all"
|
||||||
}
|
}
|
||||||
|
// fallback, empty access scope is treated as "all" access
|
||||||
return auth.AccessTokenScopeAll
|
return auth.AccessTokenScopeAll
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ func TestGrantAdditionalScopes(t *testing.T) {
|
|||||||
grantScopes string
|
grantScopes string
|
||||||
expectedScopes string
|
expectedScopes string
|
||||||
}{
|
}{
|
||||||
|
{"", "all"}, // for old tokens without scope, treat it as "all"
|
||||||
{"openid profile email", "all"},
|
{"openid profile email", "all"},
|
||||||
{"openid profile email groups", "all"},
|
{"openid profile email groups", "all"},
|
||||||
{"openid profile email all", "all"},
|
{"openid profile email all", "all"},
|
||||||
@ -22,12 +23,14 @@ func TestGrantAdditionalScopes(t *testing.T) {
|
|||||||
{"read:user read:repository", "read:repository,read:user"},
|
{"read:user read:repository", "read:repository,read:user"},
|
||||||
{"read:user write:issue public-only", "public-only,write:issue,read:user"},
|
{"read:user write:issue public-only", "public-only,write:issue,read:user"},
|
||||||
{"openid profile email read:user", "read:user"},
|
{"openid profile email read:user", "read:user"},
|
||||||
|
|
||||||
|
// TODO: at the moment invalid tokens are treated as "all" to avoid breaking 1.22 behavior (more details are in GrantAdditionalScopes)
|
||||||
{"read:invalid_scope", "all"},
|
{"read:invalid_scope", "all"},
|
||||||
{"read:invalid_scope,write:scope_invalid,just-plain-wrong", "all"},
|
{"read:invalid_scope,write:scope_invalid,just-plain-wrong", "all"},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run(test.grantScopes, func(t *testing.T) {
|
t.Run("scope:"+test.grantScopes, func(t *testing.T) {
|
||||||
result := GrantAdditionalScopes(test.grantScopes)
|
result := GrantAdditionalScopes(test.grantScopes)
|
||||||
assert.Equal(t, test.expectedScopes, string(result))
|
assert.Equal(t, test.expectedScopes, string(result))
|
||||||
})
|
})
|
||||||
|
@ -565,7 +565,7 @@ func TestOAuth_GrantScopesReadUserFailRepos(t *testing.T) {
|
|||||||
|
|
||||||
errorParsed := new(errorResponse)
|
errorParsed := new(errorResponse)
|
||||||
require.NoError(t, json.Unmarshal(errorResp.Body.Bytes(), errorParsed))
|
require.NoError(t, json.Unmarshal(errorResp.Body.Bytes(), errorParsed))
|
||||||
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s): [read:repository]")
|
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s), required=[read:repository]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
||||||
@ -708,7 +708,7 @@ func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
|
|||||||
|
|
||||||
errorParsed := new(errorResponse)
|
errorParsed := new(errorResponse)
|
||||||
require.NoError(t, json.Unmarshal(errorResp.Body.Bytes(), errorParsed))
|
require.NoError(t, json.Unmarshal(errorResp.Body.Bytes(), errorParsed))
|
||||||
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s): [read:user read:organization]")
|
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s), required=[read:user read:organization]")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOAuth_GrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
func TestOAuth_GrantScopesClaimPublicOnlyGroups(t *testing.T) {
|
||||||
|
Loading…
Reference in New Issue
Block a user