mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
feat: implement fine-grained access tokens in /teams/{id}/repos
**Breaking*: /teams/{id}/repos previously allowed read access to private
repositories even if a "public-only" access token was in-use. This has
been restricted to only return public repositories in this case.
This commit is contained in:
parent
cac675bc21
commit
0eca229d15
6 changed files with 116 additions and 5 deletions
|
|
@ -34,6 +34,8 @@ func HasTeamRepo(ctx context.Context, orgID, teamID, repoID int64) bool {
|
|||
type SearchTeamRepoOptions struct {
|
||||
db.ListOptions
|
||||
TeamID int64
|
||||
// Filters repositories based upon optional authorization restrictions.
|
||||
AuthorizationReducer repo_model.RepositoryAuthorizationReducer
|
||||
}
|
||||
|
||||
// GetRepositories returns paginated repositories in team of organization.
|
||||
|
|
@ -46,6 +48,9 @@ func GetTeamRepositories(ctx context.Context, opts *SearchTeamRepoOptions) (repo
|
|||
Where(builder.Eq{"team_id": opts.TeamID}),
|
||||
)
|
||||
}
|
||||
if opts.AuthorizationReducer != nil {
|
||||
sess = sess.Where(opts.AuthorizationReducer.RepoReadAccessFilter())
|
||||
}
|
||||
if opts.PageSize > 0 {
|
||||
sess.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -572,8 +572,9 @@ func GetTeamRepos(ctx *context.APIContext) {
|
|||
|
||||
team := ctx.Org.Team
|
||||
teamRepos, err := organization.GetTeamRepositories(ctx, &organization.SearchTeamRepoOptions{
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
TeamID: team.ID,
|
||||
ListOptions: utils.GetListOptions(ctx),
|
||||
TeamID: team.ID,
|
||||
AuthorizationReducer: ctx.Reducer,
|
||||
})
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
|
||||
|
|
@ -581,10 +582,15 @@ func GetTeamRepos(ctx *context.APIContext) {
|
|||
}
|
||||
repos := make([]*api.Repository, len(teamRepos))
|
||||
for i, repo := range teamRepos {
|
||||
permission, err := access_model.GetUserRepoPermission(ctx, repo, ctx.Doer)
|
||||
permission, err := access_model.GetUserRepoPermissionWithReducer(ctx, repo, ctx.Doer, ctx.Reducer)
|
||||
if err != nil {
|
||||
ctx.Error(http.StatusInternalServerError, "GetTeamRepos", err)
|
||||
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermissionWithReducer", err)
|
||||
return
|
||||
} else if !permission.HasAccess() {
|
||||
// It shouldn't happen that a repo is returned from GetTeamRepositories which we have no access to at all.
|
||||
// Due to the pagination of the API it doesn't make sense to skip it, as we wouldn't be giving the right
|
||||
// number of results back to the API consumer.
|
||||
ctx.Error(http.StatusInternalServerError, "InvalidAuthorizationReducer", "Repository was available from GetTeamRepositories, but not readable.")
|
||||
}
|
||||
repos[i] = convert.ToRepo(ctx, repo, permission)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,6 +295,82 @@ func TestAPITeamSearch(t *testing.T) {
|
|||
MakeRequest(t, req, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func TestAPIGetTeamReposAccessTokenResources(t *testing.T) {
|
||||
defer unittest.OverrideFixtures("tests/integration/fixtures/TestAPIGetTeamReposAccessTokenResources")()
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
var repos []api.Repository
|
||||
|
||||
// Test cases org3/repo21 (public), org3/repo3 (private), org3/repo5 (private) --
|
||||
// TestAPIGetTeamReposAccessTokenResources fixtures create a team w/ ID=26 that contains all three repos.
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
find := func() (bool, bool, bool) {
|
||||
foundRepo21 := false // public org3/repo21
|
||||
foundRepo3 := false // private org3/repo3
|
||||
foundRepo5 := false // second private repo org3/repo5 used in fine-grain testing, included as baseline
|
||||
for _, repo := range repos {
|
||||
switch repo.Name {
|
||||
case "repo21":
|
||||
foundRepo21 = true
|
||||
case "repo3":
|
||||
foundRepo3 = true
|
||||
case "repo5":
|
||||
foundRepo5 = true
|
||||
}
|
||||
}
|
||||
return foundRepo21, foundRepo3, foundRepo5
|
||||
}
|
||||
|
||||
t.Run("all access token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
allToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/teams/26/repos").AddTokenAuth(allToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo21, foundRepo3, foundRepo5 := find()
|
||||
|
||||
assert.True(t, foundRepo21) // public org3/repo21
|
||||
assert.True(t, foundRepo3) // private org3/repo3
|
||||
assert.True(t, foundRepo5) // private org3/repo5, 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.AccessTokenScopeReadOrganization)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/teams/26/repos").AddTokenAuth(publicOnlyToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo21, foundRepo3, foundRepo5 := find()
|
||||
|
||||
assert.True(t, foundRepo21) // public org3/repo21
|
||||
assert.False(t, foundRepo3) // private org3/repo3
|
||||
assert.False(t, foundRepo5) // private org3/repo5
|
||||
})
|
||||
|
||||
t.Run("specific repo access token", func(t *testing.T) {
|
||||
defer tests.PrintCurrentTest(t)()
|
||||
|
||||
repo2OnlyToken := createFineGrainedRepoAccessToken(t, "user2",
|
||||
[]auth_model.AccessTokenScope{auth_model.AccessTokenScopeReadOrganization},
|
||||
[]int64{3},
|
||||
)
|
||||
|
||||
req := NewRequest(t, "GET", "/api/v1/teams/26/repos").AddTokenAuth(repo2OnlyToken)
|
||||
resp := MakeRequest(t, req, http.StatusOK)
|
||||
DecodeJSON(t, resp, &repos)
|
||||
foundRepo21, foundRepo3, foundRepo5 := find()
|
||||
|
||||
assert.True(t, foundRepo21) // public org3/repo21, allowed as it's public and read-access only
|
||||
assert.True(t, foundRepo3) // private org3/repo3, allowed inside fine-grain
|
||||
assert.False(t, foundRepo5) // private org3/repo5, denied outside fine-grain
|
||||
})
|
||||
}
|
||||
|
||||
func TestAPIGetTeamRepo(t *testing.T) {
|
||||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
|
|
@ -325,7 +401,7 @@ func TestAPIGetTeamRepoAccessTokenResources(t *testing.T) {
|
|||
defer tests.PrepareTestEnv(t)()
|
||||
|
||||
// Test cases org3/repo21 (public), org3/repo3 (private), org3/repo5 (private) --
|
||||
// TestAPIGetTeamReposAccessTokenResources fixtures create a team w/ ID=26 that contains all three repos.
|
||||
// TestAPIGetTeamRepoAccessTokenResources fixtures create a team w/ ID=26 that contains all three repos.
|
||||
session := loginUser(t, "user2")
|
||||
|
||||
var repo api.Repository
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
-
|
||||
id: 26
|
||||
org_id: 3
|
||||
includes_all_repositories: false
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
-
|
||||
id: 20
|
||||
org_id: 3
|
||||
team_id: 26
|
||||
repo_id: 32 # org3/repo21 - public
|
||||
-
|
||||
id: 21
|
||||
org_id: 3
|
||||
team_id: 26
|
||||
repo_id: 3 # org3/repo3 - private
|
||||
-
|
||||
id: 22
|
||||
org_id: 3
|
||||
team_id: 26
|
||||
repo_id: 5 # org3/repo5 - private
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
-
|
||||
id: 30
|
||||
org_id: 3
|
||||
team_id: 26
|
||||
uid: 2
|
||||
Loading…
Add table
Add a link
Reference in a new issue