feat: implement fine-grained access tokens in /user/starred & /users/{username}/starred
This commit is contained in:
committed by
Mathieu Fenniak
parent
5582faa0c7
commit
9c748e87e1
@@ -17,13 +17,14 @@ import (
|
||||
)
|
||||
|
||||
// GetStarredRepos returns the repos starred by a particular user
|
||||
func GetStarredRepos(ctx context.Context, userID int64, private bool, listOptions db.ListOptions) ([]*Repository, error) {
|
||||
func GetStarredRepos(ctx context.Context, userID int64, private bool, listOptions db.ListOptions, reducer RepositoryAuthorizationReducer) ([]*Repository, error) {
|
||||
sess := db.GetEngine(ctx).
|
||||
Where("star.uid=?", userID).
|
||||
Join("LEFT", "star", "`repository`.id=`star`.repo_id")
|
||||
if !private {
|
||||
sess = sess.And("is_private=?", false)
|
||||
}
|
||||
sess = sess.And(reducer.RepoReadAccessFilter())
|
||||
|
||||
if listOptions.Page != 0 {
|
||||
sess = db.SetSessionPagination(sess, &listOptions)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
std_context "context"
|
||||
"net/http"
|
||||
|
||||
"forgejo.org/models/db"
|
||||
@@ -22,14 +21,18 @@ import (
|
||||
|
||||
// getStarredRepos returns the repos that the user with the specified userID has
|
||||
// starred
|
||||
func getStarredRepos(ctx std_context.Context, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) {
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, user.ID, private, listOptions)
|
||||
func getStarredRepos(ctx *context.APIContext, user *user_model.User, private bool, listOptions db.ListOptions) ([]*api.Repository, error) {
|
||||
starredRepos, err := repo_model.GetStarredRepos(ctx, user.ID, private, listOptions, ctx.Reducer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*api.Repository, len(starredRepos))
|
||||
for i, starred := range starredRepos {
|
||||
// Resource filtering is implemented above in the call to GetStarredRepos, and doesn't need to be taken into
|
||||
// account here:
|
||||
//
|
||||
// nosemgrep: forgejo-api-use-resource-GetUserRepoPermission
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, starred, user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -116,3 +116,84 @@ func TestAPIStar(t *testing.T) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIStarRepoAccessTokenResources(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
var repos []api.Repository
|
||||
|
||||
// Test cases repo1, repo2, repo16 -- create a star on each of them so that we can inspect through /starred 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/user/starred/user2/%s", r)).AddTokenAuth(writeToken),
|
||||
http.StatusNoContent)
|
||||
}
|
||||
|
||||
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/starred").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/starred").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/starred").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
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user