feat: implement fine-grained access tokens in /repos/issues/search

This commit is contained in:
Mathieu Fenniak 2026-02-15 14:17:38 -07:00
parent 2192184d9b
commit 0da4505a49
No known key found for this signature in database
2 changed files with 93 additions and 0 deletions

View file

@ -166,6 +166,8 @@ func SearchIssues(ctx *context.APIContext) {
// MySQL will return different results when sorting by null in some cases // MySQL will return different results when sorting by null in some cases
OrderBy: db.SearchOrderByAlphabetically, OrderBy: db.SearchOrderByAlphabetically,
Actor: ctx.Doer, Actor: ctx.Doer,
AuthorizationReducer: ctx.Reducer,
} }
if ctx.IsSigned { if ctx.IsSigned {
opts.Private = !ctx.PublicOnly opts.Private = !ctx.PublicOnly

View file

@ -660,6 +660,97 @@ func TestAPISearchIssuesWithLabels(t *testing.T) {
assert.Len(t, apiIssues, 2) assert.Len(t, apiIssues, 2)
} }
func TestAPISearchIssuesAccessTokenResources(t *testing.T) {
defer tests.PrepareTestEnv(t)()
var issues []*api.Issue
// Test repos: repo1 (public), repo2 (private), repo16 (private).
session := loginUser(t, "user2")
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteIssue)
// On those three test repos, create an issue with a specific title for search.
for _, repo := range []string{"repo1", "repo2", "repo16"} {
trueBool := true
// Enable issues on each target repo as well
req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/user2/%s", repo), &api.EditRepoOption{
HasIssues: &trueBool,
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusOK)
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/user2/%s/issues", repo), &api.CreateIssueOption{
Body: "body: abracadabra",
Title: "important issue",
}).AddTokenAuth(writeToken)
MakeRequest(t, req, http.StatusCreated)
}
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 _, issue := range issues {
switch issue.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.AccessTokenScopeReadIssue)
req := NewRequest(t, "GET", "/api/v1/repos/issues/search").AddTokenAuth(allToken)
resp := MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &issues)
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.AccessTokenScopeReadIssue)
req := NewRequest(t, "GET", "/api/v1/repos/issues/search").AddTokenAuth(publicOnlyToken)
resp := MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &issues)
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.AccessTokenScopeReadIssue},
[]int64{2},
)
req := NewRequest(t, "GET", "/api/v1/repos/issues/search").AddTokenAuth(repo2OnlyToken)
resp := MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &issues)
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
})
}
func TestAPIInternalAndExternalIssueTracker(t *testing.T) { func TestAPIInternalAndExternalIssueTracker(t *testing.T) {
defer tests.PrepareTestEnv(t)() defer tests.PrepareTestEnv(t)()