refactor: carry the API token on the doer (#39690)

The token a request authenticated with now lives on the doer, so
permission and visibility checks apply its scope from the doer alone.
Replaces https://github.com/go-gitea/gitea/pull/39167, following
https://github.com/go-gitea/gitea/pull/39167#issuecomment-6063839619.

1. Public-only tokens get 404 instead of 403 for hidden users, orgs and
repos, like GitHub.
2. Org ownership, site admin, repo and org creation, generation and
transfer endpoints reject public-only tokens.
3. API pull request responses set `head.repo` to `null` and
`head.repo_id` to `-1` when the caller cannot see the head repository.
4. Public-only tokens see org memberships like anonymous callers, so
concealed members stay hidden.
5. Site admin access in activity feeds, follower lists, org members,
Actions run lists and packages needs a session or an `all`-scoped token.

Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
silverwindandwxiaoguang authored and GitHub committed 2026-10-19 00:34:01 +00:00
1 parent 7e62dda791
commit 5264304a34
96 files changed
+583 -686

No files matched your search

+28
View File
@@ -142,6 +142,34 @@ func TestAPIViewPulls(t *testing.T) {
assert.Len(t, files, 1)
}))
}
t.Run("HeadRepoHiddenWhenInaccessible", func(t *testing.T) {
session := loginUser(t, "user31")
forkToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{}).AddTokenAuth(forkToken), http.StatusAccepted)
visible := DecodeJSON(t, MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/pulls", &api.CreatePullRequestOption{
Head: "user31:branch2", Base: "master", Title: "pull from a private user's fork",
}).AddTokenAuth(forkToken), http.StatusCreated), &api.PullRequest{})
require.NotNil(t, visible.Head.Repository)
require.NotEmpty(t, visible.Head.Sha)
prURL := fmt.Sprintf("/api/v1/repos/user2/repo1/pulls/%d", visible.Index)
var listed []*api.PullRequest
DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", "/api/v1/repos/user2/repo1/pulls?poster=user31"), http.StatusOK), &listed)
require.Len(t, listed, 1)
for _, hidden := range []*api.PullRequest{
DecodeJSON(t, MakeRequest(t, NewRequest(t, "GET", prURL), http.StatusOK), &api.PullRequest{}),
listed[0],
} {
assert.Nil(t, hidden.Head.Repository)
assert.EqualValues(t, -1, hidden.Head.RepoID)
assert.Equal(t, visible.Head.Name, hidden.Head.Name)
assert.Equal(t, visible.Head.Sha, hidden.Head.Sha)
}
pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{BaseRepoID: 1, Index: visible.Index})
assert.NotNil(t, convert.ToPayloadPullRequest(t.Context(), pr, nil).Head.Repository)
})
}
func TestAPIViewPullsByBaseHead(t *testing.T) {