mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
feat: implement fine-grained access tokens in /user/repos
**Breaking**: a user's own public-only access tokens were previously visible in the `/users/repos` API. This access has been removed in this change.
This commit is contained in:
parent
4bd81d3363
commit
a309db27f2
2 changed files with 87 additions and 7 deletions
|
|
@ -115,11 +115,12 @@ func ListMyRepos(ctx *context.APIContext) {
|
|||
// "$ref": "#/responses/validationError"
|
||||
|
||||
opts := &repo_model.SearchRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
Actor: ctx.Doer,
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Private: ctx.IsSigned,
|
||||
IncludeDescription: true,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
Actor: ctx.Doer,
|
||||
OwnerID: ctx.Doer.ID,
|
||||
Private: ctx.IsSigned,
|
||||
IncludeDescription: true,
|
||||
AuthorizationReducer: ctx.Reducer,
|
||||
}
|
||||
orderBy := ctx.FormTrim("order_by")
|
||||
switch orderBy {
|
||||
|
|
@ -148,9 +149,14 @@ func ListMyRepos(ctx *context.APIContext) {
|
|||
ctx.Error(http.StatusInternalServerError, "LoadOwner", err)
|
||||
return
|
||||
}
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetUserRepoPermissionWithReducer(ctx, repo, ctx.Doer, ctx.Reducer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermissionWithReducer", err)
|
||||
} else if !permission.HasAccess() {
|
||||
// It shouldn't happen that a repo is returned from SearchRepository which we have no access to at all. Due
|
||||
// to the pagination of the API it doesn't make sense to skip it, as we wouldn't be giving the right number
|
||||
// of results back to the API consumer.
|
||||
ctx.Error(http.StatusInternalServerError, "InvalidAuthorizationReducer", "Repository was available from SearchRepository, but not readable.")
|
||||
}
|
||||
results[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -966,3 +966,77 @@ func TestAPIListOwnRepoSorting(t *testing.T) {
|
|||
assert.Equal(t, "test_workflows", repos[1].Name)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIListOwnRepoAccessTokenResources(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
var repos []api.Repository
|
||||
|
||||
// Test cases repo1 (public), repo2 (private), repo16 (private).
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
find := func() (bool, bool, bool) {
|
||||
foundRepo1 := false // public user2/repo1
|
||||
foundRepo2 := false // private user2/repo2
|
||||
foundRepo16 := false // second private repo user2/repo16 used in fine-grain testing, included as baseline
|
||||
for _, repo := range repos {
|
||||
switch repo.Name {
|
||||
case "repo1":
|
||||
foundRepo1 = true
|
||||
case "repo2":
|
||||
foundRepo2 = true
|
||||
case "repo16":
|
||||
foundRepo16 = true
|
||||
}
|
||||
}
|
||||
return foundRepo1, foundRepo2, foundRepo16
|
||||
}
|
||||
|
||||
t.Run("all access token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
allToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(allToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo1, foundRepo2, foundRepo16 := find()
|
||||
|
||||
assert.True(t, foundRepo1) // public user2/repo1
|
||||
assert.True(t, foundRepo2) // private user2/repo2
|
||||
assert.True(t, foundRepo16) // private user2/repo16, used in fine-grain testing, included as baseline
|
||||
})
|
||||
|
||||
t.Run("public-only access token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(publicOnlyToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo1, foundRepo2, foundRepo16 := find()
|
||||
|
||||
assert.True(t, foundRepo1) // public user2/repo1
|
||||
assert.False(t, foundRepo2) // private user2/repo2
|
||||
assert.False(t, foundRepo16) // private user2/repo16
|
||||
})
|
||||
|
||||
t.Run("specific repo access token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
repo2OnlyToken := createFineGrainedRepoAccessToken(t, "user2",
|
||||
[]auth_model.AccessTokenScope{auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository},
|
||||
[]int64{2},
|
||||
)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(repo2OnlyToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo1, foundRepo2, foundRepo16 := find()
|
||||
|
||||
assert.True(t, foundRepo1) // public user2/repo1, allowed as it's public and read-access only
|
||||
assert.True(t, foundRepo2) // private user2/repo2, allowed inside fine-grain
|
||||
assert.False(t, foundRepo16) // private user2/repo16, denied outside fine-grain
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue