Files
Gitea/tests/integration/api_user_visibility_test.go
T
Giteabot 1e86e1e45a fix(api): hide limited users from restricted viewers (#39004) (#39039)
Backport #39004 by @bircni

Use the canonical profile-visibility check for user API content and
prevent restricted users from enumerating public repositories owned by
limited users.

This keeps feeds, heatmaps, keys, and issue search consistent with
profile visibility.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
2026-08-22 10:55:16 +00:00

77 lines
2.7 KiB
Go

// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/url"
"testing"
auth_model "gitea.dev/models/auth"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
api "gitea.dev/modules/structs"
"gitea.dev/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAPIRestrictedUserLimitedOwner(t *testing.T) {
defer tests.PrepareTestEnv(t)()
owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
owner.Visibility = api.VisibleTypeLimited
require.NoError(t, user_model.UpdateUserCols(t.Context(), owner, "visibility"))
restrictedToken := getUserToken(t, "user29", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadOrganization)
for _, path := range []string{
"/api/v1/users/user2/activities/feeds",
"/api/v1/users/user2/heatmap",
"/api/v1/users/user2/keys",
"/api/v1/users/user2/gpg_keys",
"/api/v1/users/user2/orgs",
} {
req := NewRequest(t, "GET", path).AddTokenAuth(restrictedToken)
MakeRequest(t, req, http.StatusNotFound)
}
issueSearch := url.URL{Path: "/api/v1/repos/issues/search"}
issueSearch.RawQuery = url.Values{"owner": {"user2"}}.Encode()
req := NewRequest(t, "GET", issueSearch.String()).AddTokenAuth(restrictedToken)
resp := MakeRequest(t, req, http.StatusOK)
assert.Empty(t, DecodeJSON(t, resp, []*api.Issue{}))
issueSearch.RawQuery = url.Values{"limit": {"100"}, "type": {"issues"}}.Encode()
req = NewRequest(t, "GET", issueSearch.String()).AddTokenAuth(restrictedToken)
resp = MakeRequest(t, req, http.StatusOK)
issues := DecodeJSON(t, resp, []*api.Issue{})
require.NotEmpty(t, issues)
for _, issue := range issues {
assert.NotEqual(t, owner.Name, issue.Repo.Owner)
}
restrictedSession := loginUser(t, "user29")
req = NewRequest(t, "GET", "/issues/search?owner=user2")
resp = restrictedSession.MakeRequest(t, req, http.StatusOK)
assert.Empty(t, DecodeJSON(t, resp, []*api.Issue{}))
viewerToken := getUserToken(t, "user4", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopeReadOrganization)
for _, path := range []string{
"/api/v1/users/user2/activities/feeds",
"/api/v1/users/user2/heatmap",
"/api/v1/users/user2/keys",
"/api/v1/users/user2/gpg_keys",
"/api/v1/users/user2/orgs",
} {
req := NewRequest(t, "GET", path).AddTokenAuth(viewerToken)
MakeRequest(t, req, http.StatusOK)
}
issueSearch.RawQuery = url.Values{"owner": {"user2"}}.Encode()
req = NewRequest(t, "GET", issueSearch.String()).AddTokenAuth(viewerToken)
resp = MakeRequest(t, req, http.StatusOK)
assert.NotEmpty(t, DecodeJSON(t, resp, []*api.Issue{}))
}