Backport #37583 by @lunny Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Nicolas <bircni@icloud.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
@@ -57,14 +57,14 @@ func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckRepoScopedToken check whether personal access token has repo scope
|
// CheckRepoScopedToken checks whether the authenticated API token has repo scope.
|
||||||
func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) {
|
func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) {
|
||||||
if !ctx.IsBasicAuth || ctx.Data["IsApiToken"] != true {
|
if ctx.Data["IsApiToken"] != true {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope)
|
||||||
if ok { // it's a personal access token but not oauth2 token
|
if ok {
|
||||||
var scopeMatched bool
|
var scopeMatched bool
|
||||||
|
|
||||||
requiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository)
|
requiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository)
|
||||||
@@ -76,7 +76,7 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if publicOnly && repo.IsPrivate {
|
if publicOnly && repo != nil && repo.IsPrivate {
|
||||||
ctx.HTTPError(http.StatusForbidden)
|
ctx.HTTPError(http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/test"
|
"code.gitea.io/gitea/modules/test"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@@ -20,6 +23,7 @@ import (
|
|||||||
func TestGitSmartHTTP(t *testing.T) {
|
func TestGitSmartHTTP(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
testGitSmartHTTP(t, u)
|
testGitSmartHTTP(t, u)
|
||||||
|
testGitSmartHTTPTokenScopes(t)
|
||||||
testRenamedRepoRedirect(t)
|
testRenamedRepoRedirect(t)
|
||||||
testGitArchiveRemote(t, u)
|
testGitArchiveRemote(t, u)
|
||||||
})
|
})
|
||||||
@@ -80,6 +84,42 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testGitSmartHTTPTokenScopes(t *testing.T) {
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerName: "user2", Name: "repo2"})
|
||||||
|
require.True(t, repo.IsPrivate)
|
||||||
|
|
||||||
|
session := loginUser(t, "user2")
|
||||||
|
badToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification)
|
||||||
|
readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
t.Run("upload-pack requires read repository scope", func(t *testing.T) {
|
||||||
|
path := "/user2/repo2/info/refs?service=git-upload-pack"
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", path).AddBasicAuth(badToken, "x-oauth-basic"), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(badToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(readToken), http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "refs/heads/master")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("receive-pack requires write repository scope", func(t *testing.T) {
|
||||||
|
path := "/user2/repo2/info/refs?service=git-receive-pack"
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", path).AddBasicAuth(readToken, "x-oauth-basic"), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(readToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(writeToken), http.StatusOK)
|
||||||
|
assert.Contains(t, resp.Body.String(), "refs/heads/master")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("public-only scope rejects private repo", func(t *testing.T) {
|
||||||
|
path := "/user2/repo2/info/refs?service=git-upload-pack"
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func testRenamedRepoRedirect(t *testing.T) {
|
func testRenamedRepoRedirect(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user