mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-28 02:38:44 +09:00
backport #37118 This PR closes remaining `public-only` token gaps in the API by making the restriction apply consistently across repository, organization, activity, notification, and authenticated `/api/v1/user/...` routes. Previously, `public-only` tokens were still able to: - receive private results from some list/search/self endpoints, - access repository data through ID-based lookups, - and reach several authenticated self routes that should remain unavailable for public-only access. This change treats `public-only` as a cross-cutting visibility boundary: - list/search endpoints now filter private resources consistently, - repository lookups enforce the same restriction even when addressed indirectly, - and self routes that inherently expose or mutate private account state now reject `public-only` tokens. --- Generated by a coding agent with Codex 5.2 Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: Nicolas <bircni@icloud.com>
This commit is contained in:
@@ -108,7 +108,7 @@ func testAPIListIssuesPublicOnly(t *testing.T) {
|
||||
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func testAPICreateIssue(t *testing.T) {
|
||||
|
||||
@@ -212,3 +212,23 @@ func TestAPINotificationPUT(t *testing.T) {
|
||||
assert.True(t, apiNL[0].Unread)
|
||||
assert.False(t, apiNL[0].Pinned)
|
||||
}
|
||||
|
||||
func TestAPINotificationPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||
thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5})
|
||||
|
||||
token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/notifications").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/notifications/new").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
|
||||
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)).
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
107
tests/integration/api_public_only_test.go
Normal file
107
tests/integration/api_public_only_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAPIUserReposPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/user/repos").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
var repos []api.Repository
|
||||
DecodeJSON(t, resp, &repos)
|
||||
assert.NotEmpty(t, repos)
|
||||
for _, repo := range repos {
|
||||
assert.False(t, repo.Private)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/users/user2/repos").
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
assert.NotEmpty(t, repos)
|
||||
for _, repo := range repos {
|
||||
assert.False(t, repo.Private)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
}
|
||||
|
||||
func repoNames(repos []api.Repository) []string {
|
||||
names := make([]string, 0, len(repos))
|
||||
for _, repo := range repos {
|
||||
names = append(names, repo.FullName)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func TestAPIRepoByIDPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/repositories/1").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/repositories/2").
|
||||
AddTokenAuth(token)
|
||||
MakeRequest(t, req, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func TestAPIActivityFeedsPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser)
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var activities []api.Activity
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assert.NotEmpty(t, activities)
|
||||
|
||||
publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds").
|
||||
AddTokenAuth(publicToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assertPublicActivitiesOnly(t, activities)
|
||||
|
||||
orgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization)
|
||||
req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds").
|
||||
AddTokenAuth(orgToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assert.NotEmpty(t, activities)
|
||||
|
||||
publicOrgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly)
|
||||
req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds").
|
||||
AddTokenAuth(publicOrgToken)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &activities)
|
||||
assertPublicActivitiesOnly(t, activities)
|
||||
}
|
||||
|
||||
func assertPublicActivitiesOnly(t *testing.T, activities []api.Activity) {
|
||||
t.Helper()
|
||||
|
||||
for _, activity := range activities {
|
||||
assert.False(t, activity.IsPrivate)
|
||||
if activity.Repo != nil {
|
||||
assert.False(t, activity.Repo.Private)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,10 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||
session := loginUser(t, user1.LowerName)
|
||||
|
||||
// public only token should be forbidden
|
||||
// public-only token cannot see a private repo
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository)
|
||||
link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo
|
||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||
@@ -46,7 +46,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
assert.Equal(t, "master", branches[1].Name)
|
||||
|
||||
link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name))
|
||||
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
||||
bs, err = io.ReadAll(resp.Body)
|
||||
@@ -55,7 +55,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
assert.NoError(t, json.Unmarshal(bs, &branch))
|
||||
assert.Equal(t, "test_branch", branch.Name)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
@@ -81,7 +81,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
||||
|
||||
link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name))
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -155,3 +155,44 @@ func TestMyOrgs(t *testing.T) {
|
||||
},
|
||||
}, orgs)
|
||||
}
|
||||
|
||||
func TestMyOrgsPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
normalUsername := "user2"
|
||||
token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/user/orgs").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
var orgs []*api.Organization
|
||||
DecodeJSON(t, resp, &orgs)
|
||||
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||
org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"})
|
||||
|
||||
assert.Equal(t, []*api.Organization{
|
||||
{
|
||||
ID: 17,
|
||||
Name: org17.Name,
|
||||
UserName: org17.Name,
|
||||
FullName: org17.FullName,
|
||||
Email: org17.Email,
|
||||
AvatarURL: org17.AvatarLink(t.Context()),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Name: org3.Name,
|
||||
UserName: org3.Name,
|
||||
FullName: org3.FullName,
|
||||
Email: org3.Email,
|
||||
AvatarURL: org3.AvatarLink(t.Context()),
|
||||
Description: "",
|
||||
Website: "",
|
||||
Location: "",
|
||||
Visibility: "public",
|
||||
},
|
||||
}, orgs)
|
||||
}
|
||||
|
||||
177
tests/integration/api_user_public_only_test.go
Normal file
177
tests/integration/api_user_public_only_test.go
Normal file
@@ -0,0 +1,177 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
auth_model "code.gitea.io/gitea/models/auth"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIPublicOnlySelfUserRoutes(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user31"})
|
||||
require.True(t, privateUser.Visibility.IsPrivate())
|
||||
|
||||
privateSession := loginUser(t, privateUser.Name)
|
||||
privateReadUserToken := getTokenForLoggedInUser(t, privateSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeReadUser,
|
||||
)
|
||||
privateWriteUserToken := getTokenForLoggedInUser(t, privateSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeWriteUser,
|
||||
)
|
||||
|
||||
t.Run("PrivateProfileForbidden", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user31").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("PrivateSensitiveSelfRoutesForbidden", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
hideEmail := true
|
||||
settingsReq := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
|
||||
HideEmail: &hideEmail,
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, settingsReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/emails").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
emailReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{
|
||||
Emails: []string{"user31-public-only@example.com"},
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, emailReq, http.StatusForbidden)
|
||||
|
||||
keyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", api.CreateKeyOption{
|
||||
Title: "public-only-private-key",
|
||||
Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, keyReq, http.StatusForbidden)
|
||||
|
||||
oauthReq := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &api.CreateOAuth2ApplicationOptions{
|
||||
Name: "public-only-private-oauth-app",
|
||||
RedirectURIs: []string{"https://example.com/callback"},
|
||||
ConfidentialClient: true,
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, oauthReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/gpg_keys").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
gpgKeyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_keys", &api.CreateGPGKeyOption{
|
||||
ArmoredKey: "-----BEGIN PGP PUBLIC KEY BLOCK-----\ncomment\n-----END PGP PUBLIC KEY BLOCK-----",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, gpgKeyReq, http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/gpg_key_token").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
gpgVerifyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_key_verify", &api.VerifyGPGKeyOption{
|
||||
KeyID: "deadbeef",
|
||||
Signature: "invalid-signature",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, gpgVerifyReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/actions/variables").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/actions/secrets/PRIVATE_SECRET").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
variableReq := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/variables/PRIVATE_VAR", api.CreateVariableOption{
|
||||
Value: "private-value",
|
||||
Description: "must stay private",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, variableReq, http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/hooks").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
hookReq := NewRequestWithJSON(t, "POST", "/api/v1/user/hooks", api.CreateHookOption{
|
||||
Type: "gitea",
|
||||
Config: api.CreateHookOptionConfig{
|
||||
"content_type": "json",
|
||||
"url": "http://example.com/",
|
||||
},
|
||||
Name: "public-only-private-hook",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, hookReq, http.StatusForbidden)
|
||||
|
||||
avatarReq := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &api.UpdateUserAvatarOption{
|
||||
Image: "aGVsbG8=",
|
||||
}).AddTokenAuth(privateWriteUserToken)
|
||||
MakeRequest(t, avatarReq, http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/avatar").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/stopwatches").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/teams").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/blocks").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/user/blocks/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/user/following/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/following/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||
})
|
||||
|
||||
t.Run("PublicRepoRoutesFilterAndRejectMutations", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
publicSession := loginUser(t, "user2")
|
||||
fullWriteRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||
auth_model.AccessTokenScopeWriteUser,
|
||||
auth_model.AccessTokenScopeWriteRepository,
|
||||
)
|
||||
publicOnlyReadRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeReadUser,
|
||||
auth_model.AccessTokenScopeReadRepository,
|
||||
)
|
||||
publicOnlyWriteRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||
auth_model.AccessTokenScopePublicOnly,
|
||||
auth_model.AccessTokenScopeWriteUser,
|
||||
auth_model.AccessTokenScopeWriteRepository,
|
||||
)
|
||||
|
||||
publicRepoName := "public-only-visible-self-repo"
|
||||
privateRepoName := "public-only-hidden-self-repo"
|
||||
|
||||
resp := MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: publicRepoName,
|
||||
Private: false,
|
||||
}).AddTokenAuth(fullWriteRepoToken), http.StatusCreated)
|
||||
publicRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
require.Equal(t, "user2/"+publicRepoName, publicRepo.FullName)
|
||||
|
||||
resp = MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: privateRepoName,
|
||||
Private: true,
|
||||
}).AddTokenAuth(fullWriteRepoToken), http.StatusCreated)
|
||||
privateRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||
require.Equal(t, "user2/"+privateRepoName, privateRepo.FullName)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "GET", "/api/v1/repos/user2/"+privateRepoName).AddTokenAuth(publicOnlyReadRepoToken), http.StatusNotFound)
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(publicOnlyReadRepoToken), http.StatusOK)
|
||||
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||
|
||||
foundPublicRepo := false
|
||||
for _, repo := range repos {
|
||||
require.NotEqual(t, privateRepo.FullName, repo.FullName)
|
||||
if repo.FullName == publicRepo.FullName {
|
||||
foundPublicRepo = true
|
||||
}
|
||||
}
|
||||
require.True(t, foundPublicRepo)
|
||||
|
||||
MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||
Name: "public-only-rejected-self-repo",
|
||||
Private: false,
|
||||
}).AddTokenAuth(publicOnlyWriteRepoToken), http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"code.gitea.io/gitea/tests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAPIStar(t *testing.T) {
|
||||
@@ -155,3 +156,24 @@ func TestAPIStarDisabled(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusForbidden)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIStarPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||
req := NewRequest(t, "GET", "/api/v1/user/starred").
|
||||
AddTokenAuth(token)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
|
||||
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||
if assert.Len(t, repos, 1) {
|
||||
assert.Equal(t, "user5/repo4", repos[0].FullName)
|
||||
}
|
||||
|
||||
req = NewRequest(t, "GET", "/api/v1/users/user2/starred").
|
||||
AddTokenAuth(token)
|
||||
resp = MakeRequest(t, req, http.StatusOK)
|
||||
repos = DecodeJSON(t, resp, []api.Repository{})
|
||||
require.Len(t, repos, 1)
|
||||
assert.Equal(t, "user5/repo4", repos[0].FullName)
|
||||
}
|
||||
|
||||
@@ -94,3 +94,28 @@ func TestAPIWatch(t *testing.T) {
|
||||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIWatchPublicOnly(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
session := loginUser(t, "user1")
|
||||
writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadUser)
|
||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(writeRepoToken), http.StatusOK)
|
||||
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(writeRepoToken), http.StatusOK)
|
||||
|
||||
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK)
|
||||
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||
for _, r := range repos {
|
||||
assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
|
||||
resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK)
|
||||
repos = DecodeJSON(t, resp, []api.Repository{})
|
||||
for _, r := range repos {
|
||||
assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName)
|
||||
}
|
||||
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user