mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
feat: implement fine-grained access tokens in /user/subscriptions & /users/{username}/subscriptions
This commit is contained in:
parent
9c748e87e1
commit
c89504d573
3 changed files with 89 additions and 4 deletions
|
|
@ -38,7 +38,7 @@ func GetStarredRepos(ctx context.Context, userID int64, private bool, listOption
|
|||
}
|
||||
|
||||
// GetWatchedRepos returns the repos watched by a particular user
|
||||
func GetWatchedRepos(ctx context.Context, userID int64, private bool, listOptions db.ListOptions) ([]*Repository, int64, error) {
|
||||
func GetWatchedRepos(ctx context.Context, userID int64, private bool, listOptions db.ListOptions, reducer RepositoryAuthorizationReducer) ([]*Repository, int64, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where("watch.user_id=?", userID).
|
||||
And("`watch`.mode<>?", WatchModeDont).
|
||||
|
|
@ -46,6 +46,7 @@ func GetWatchedRepos(ctx context.Context, userID int64, private bool, listOption
|
|||
if !private {
|
||||
sess = sess.And("is_private=?", false)
|
||||
}
|
||||
sess = sess.And(reducer.RepoReadAccessFilter())
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
std_context "context"
|
||||
"net/http"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
|
|
@ -18,14 +17,18 @@ import (
|
|||
)
|
||||
|
||||
// getWatchedRepos returns the repos that the user with the specified userID is watching
|
||||
func getWatchedRepos(ctx std_context.Context, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, int64, error) {
|
||||
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, user.ID, private, listOptions)
|
||||
func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, int64, error) {
|
||||
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, user.ID, private, listOptions, ctx.Reducer)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(watchedRepos))
|
||||
for i, watched := range watchedRepos {
|
||||
// Resource filtering is implemented above in the call to GetWatchedRepos, and doesn't need to be taken into
|
||||
// account here:
|
||||
//
|
||||
// nosemgrep: forgejo-api-use-resource-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, watched, user)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
|
|
|
|||
|
|
@ -86,3 +86,84 @@ func TestAPIWatch(t *testing.T) {
|
|||
MakeRequest(t, req, http.StatusNoContent)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIWatchRepoAccessTokenResources(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
var repos []api.Repository
|
||||
|
||||
// Test cases repo1, repo2, repo16 -- create a subscription on each of them so that we can inspect through
|
||||
// /subscriptions and see if the repos are visible or not with different access tokens.
|
||||
session := loginUser(t, "user2")
|
||||
writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||
for _, r := range []string{"repo1", "repo2", "repo16"} {
|
||||
MakeRequest(t,
|
||||
NewRequest(t, "PUT", fmt.Sprintf("/api/v1/repos/user2/%s/subscription", r)).AddTokenAuth(writeToken),
|
||||
http.StatusOK)
|
||||
}
|
||||
|
||||
find := func() (bool, bool, bool) {
|
||||
foundRepo1 := false // public repo1
|
||||
foundRepo2 := false // private repo2
|
||||
foundRepo16 := false // second public repo 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)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user2/subscriptions").AddTokenAuth(allToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo1, foundRepo2, foundRepo16 := find()
|
||||
|
||||
assert.True(t, foundRepo1) // public repo1
|
||||
assert.True(t, foundRepo2) // private repo2
|
||||
assert.True(t, foundRepo16) // private 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)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user2/subscriptions").AddTokenAuth(publicOnlyToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo1, foundRepo2, foundRepo16 := find()
|
||||
|
||||
assert.True(t, foundRepo1) // public repo1
|
||||
assert.False(t, foundRepo2) // private repo2
|
||||
assert.False(t, foundRepo16) // private 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},
|
||||
[]int64{2},
|
||||
)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/users/user2/subscriptions").AddTokenAuth(repo2OnlyToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo1, foundRepo2, foundRepo16 := find()
|
||||
|
||||
assert.True(t, foundRepo1) // public repo1, allowed as it's public and read-access only
|
||||
assert.True(t, foundRepo2) // private repo2, allowed inside fine-grain
|
||||
assert.False(t, foundRepo16) // private repo16, denied outside fine-grain
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue