diff --git a/routers/api/v1/user/repo.go b/routers/api/v1/user/repo.go index 94dd3931e4..54e18b1aae 100644 --- a/routers/api/v1/user/repo.go +++ b/routers/api/v1/user/repo.go @@ -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) } diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 2b48bd329e..72ebd1cb71 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -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 + }) +}