diff --git a/release-notes/11736.md b/release-notes/11736.md new file mode 100644 index 0000000000..eb4d5d1a51 --- /dev/null +++ b/release-notes/11736.md @@ -0,0 +1 @@ +The template generation (`POST /repos/{template_owner}/{template_repo}/generate`) and repository deletion (`DELETE /repos/{username}/{reponame}`) APIs have been updated to require the same permission scope as creating a new repository. Either `write:user` or `write:organization` is required, depending on the owner of the repository being created or deleted. diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index f551f20c6a..2c10464438 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -358,6 +358,16 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC } } +// Middleware that dynamically checks either the organization or user scope, depending on the owner type of the +// repository (requires `repoAssignment()` middleware to be used before this). +func tokenRequiresRepoOwnerScope(ctx *context.APIContext) { + if ctx.Repo.Owner.IsOrganization() { + tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization)(ctx) + } else { + tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)(ctx) + } +} + // Contexter middleware already checks token for user sign in process. func reqToken() func(ctx *context.APIContext) { return func(ctx *context.APIContext) { @@ -1109,6 +1119,10 @@ func Routes() *web.Route { // FIXME: Don't expose repository id outside of the system m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID) + // Needs to be extracted from the larger `/repos` group because deleting a repo isn't protected by + // `AccessTokenScopeCategoryRepository`; it's protected by either the User or Organization scope. + m.Delete("/repos/{username}/{reponame}", repoAssignment(), tokenRequiresRepoOwnerScope, reqOwner(), repo.Delete) + // Repos (requires repo scope) m.Group("/repos", func() { m.Get("/search", repo.Search) @@ -1120,12 +1134,12 @@ func Routes() *web.Route { m.Get("/compare/*", reqRepoReader(unit.TypeCode), repo.CompareDiff) m.Combo("").Get(reqAnyRepoReader(), repo.Get). - Delete(reqToken(), reqOwner(), repo.Delete). Patch(reqToken(), reqAdmin(), bind(api.EditRepoOption{}), repo.Edit) - m.Post("/convert", reqOwner(), repo.Convert) + + m.Post("/convert", reqOwner(), reqAdmin(), repo.Convert) m.Post("/generate", reqToken(), reqRepoReader(unit.TypeCode), bind(api.GenerateRepoOption{}), repo.Generate) m.Group("/transfer", func() { - m.Post("", reqOwner(), bind(api.TransferRepoOption{}), repo.Transfer) + m.Post("", reqOwner(), reqAdmin(), bind(api.TransferRepoOption{}), repo.Transfer) m.Post("/accept", repo.AcceptTransfer) m.Post("/reject", repo.RejectTransfer) }, reqToken()) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index 5f24f44f90..5c11a2380f 100644 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -13,6 +13,7 @@ import ( "time" activities_model "forgejo.org/models/activities" + auth_model "forgejo.org/models/auth" "forgejo.org/models/db" "forgejo.org/models/organization" "forgejo.org/models/perm" @@ -404,10 +405,10 @@ func Generate(ctx *context.APIContext) { return } - ctxUser := ctx.Doer + targetOwner := ctx.Doer var err error - if form.Owner != ctxUser.Name { - ctxUser, err = user_model.GetUserByName(ctx, form.Owner) + if form.Owner != targetOwner.Name { + targetOwner, err = user_model.GetUserByName(ctx, form.Owner) if err != nil { if user_model.IsErrUserNotExist(err) { ctx.JSON(http.StatusNotFound, map[string]any{ @@ -420,13 +421,13 @@ func Generate(ctx *context.APIContext) { return } - if !ctx.IsUserSiteAdmin() && !ctxUser.IsOrganization() { + if !ctx.IsUserSiteAdmin() && !targetOwner.IsOrganization() { ctx.Error(http.StatusForbidden, "", "Only admin can generate repository for other user.") return } if !ctx.IsUserSiteAdmin() { - canCreate, err := organization.OrgFromUser(ctxUser).CanCreateOrgRepo(ctx, ctx.Doer.ID) + canCreate, err := organization.OrgFromUser(targetOwner).CanCreateOrgRepo(ctx, ctx.Doer.ID) if err != nil { ctx.ServerError("CanCreateOrgRepo", err) return @@ -435,13 +436,23 @@ func Generate(ctx *context.APIContext) { return } } + + context.CheckRuntimeDeterminedScope(ctx, auth_model.AccessTokenScopeCategoryOrganization, auth_model.Write, "token requires scope write:organization to create a repository owned by a user") + if ctx.Written() { + return + } + } else { + context.CheckRuntimeDeterminedScope(ctx, auth_model.AccessTokenScopeCategoryUser, auth_model.Write, "token requires scope write:user to create a repository owned by a user") + if ctx.Written() { + return + } } - if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, ctxUser.ID, ctxUser.Name) { + if !ctx.CheckQuota(quota_model.LimitSubjectSizeReposAll, targetOwner.ID, targetOwner.Name) { return } - repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, ctxUser, ctx.Repo.Repository, opts) + repo, err := repo_service.GenerateRepository(ctx, ctx.Doer, targetOwner, ctx.Repo.Repository, opts) if err != nil { if repo_model.IsErrRepoAlreadyExist(err) { ctx.Error(http.StatusConflict, "", "The repository with the same name already exists.") @@ -453,7 +464,7 @@ func Generate(ctx *context.APIContext) { } return } - log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) + log.Trace("Repository generated [%d]: %s/%s", repo.ID, targetOwner.Name, repo.Name) ctx.JSON(http.StatusCreated, convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: perm.AccessModeOwner})) } diff --git a/services/context/permission.go b/services/context/permission.go index b1d02f135c..49504e5043 100644 --- a/services/context/permission.go +++ b/services/context/permission.go @@ -185,3 +185,23 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_ } } } + +func CheckRuntimeDeterminedScope(ctx *APIContext, scopeCategory auth_model.AccessTokenScopeCategory, level auth_model.AccessTokenScopeLevel, msg string) { + scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) + if ok { + var scopeMatched bool + + requiredScopes := auth_model.GetRequiredScopes(level, scopeCategory) + + scopeMatched, err := scope.HasScope(requiredScopes...) + if err != nil { + ctx.ServerError("HasScope", err) + return + } + + if !scopeMatched { + ctx.Error(http.StatusForbidden, "!scopeMatched", msg) + return + } + } +} diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index d252d122a0..d10bc21799 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -172,7 +172,7 @@ jobs: }) } - httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteRepository) + httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteUser) doAPIDeleteRepository(httpContext)(t) }) } @@ -357,7 +357,7 @@ jobs: }) } - httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteRepository) + httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteUser) doAPIDeleteRepository(httpContext)(t) }) } @@ -409,7 +409,7 @@ jobs: apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) runner := newMockRunner() runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}) @@ -620,7 +620,7 @@ func TestActionsEphemeral(t *testing.T) { apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) - user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) runner := newMockRunner() runner.registerAsEphemeralRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}) diff --git a/tests/integration/actions_log_test.go b/tests/integration/actions_log_test.go index 14cf74d00c..03480d6afb 100644 --- a/tests/integration/actions_log_test.go +++ b/tests/integration/actions_log_test.go @@ -159,7 +159,7 @@ jobs: }) } - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteUser) doAPIDeleteRepository(httpContext)(t) }) } @@ -275,7 +275,7 @@ jobs: ) } - httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteRepository) + httpContext := NewAPITestContext(t, user2.Name, repo.Name, auth_model.AccessTokenScopeWriteUser) doAPIDeleteRepository(httpContext)(t) }) } diff --git a/tests/integration/actions_notifications_test.go b/tests/integration/actions_notifications_test.go index 78ab79e72f..70a5eaa741 100644 --- a/tests/integration/actions_notifications_test.go +++ b/tests/integration/actions_notifications_test.go @@ -80,7 +80,7 @@ jobs: actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) assert.Equal(t, testCase.notifyEmail, actionRun.NotifyEmail) - httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteRepository) + httpContext := NewAPITestContext(t, user2.Name, apiRepo.Name, auth_model.AccessTokenScopeWriteUser) doAPIDeleteRepository(httpContext)(t) }) } diff --git a/tests/integration/api_repo_convert_test.go b/tests/integration/api_repo_convert_test.go index fb9756f975..eeec8679bb 100644 --- a/tests/integration/api_repo_convert_test.go +++ b/tests/integration/api_repo_convert_test.go @@ -64,3 +64,19 @@ func TestAPIConvert(t *testing.T) { repo4edited := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4}) assert.False(t, repo4edited.IsMirror) } + +// This test verifies that a repo-specific access token with `write:repository` scope is not a sufficient scope to edit +// the settings of a repository that is within its repo-specific list. +func TestAPIConvertAccessTokenResources(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo5 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + org3 := "org3" + + repoSpecificToken := createFineGrainedRepoAccessToken(t, "user2", + []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteRepository}, + []int64{repo5.ID}, + ) + req := NewRequest(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/convert", org3, repo5.Name)).AddTokenAuth(repoSpecificToken) + MakeRequest(t, req, http.StatusForbidden) +} diff --git a/tests/integration/api_repo_edit_test.go b/tests/integration/api_repo_edit_test.go index 341cb0961f..dd8699b59c 100644 --- a/tests/integration/api_repo_edit_test.go +++ b/tests/integration/api_repo_edit_test.go @@ -394,3 +394,21 @@ func TestAPIRepoEdit(t *testing.T) { assert.Equal(t, "rebase", apiRepo.DefaultUpdateStyle) }) } + +// This test verifies that a repo-specific access token with `write:repository` scope is not a sufficient scope to edit +// the settings of a repository that is within its repo-specific list. +func TestAPIRepoEditAccessTokenResources(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo2OnlyToken := createFineGrainedRepoAccessToken(t, "user2", + []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteRepository}, + []int64{2}, + ) + desc := "here's a new description" + req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/repos/user2/repo2"), + &api.EditRepoOption{ + Description: &desc, + }). + AddTokenAuth(repo2OnlyToken) + MakeRequest(t, req, http.StatusForbidden) +} diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index 9587383862..60c662c28a 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -824,6 +824,75 @@ func testAPIRepoCreateConflict(t *testing.T, u *url.URL) { }) } +func TestAPIRepoCreateDenied(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // This test verifies that `write:repository` is not a sufficient scope to create a repository. If it was, then + // repo-specific access tokens would be able to create new repositories. + session := loginUser(t, "user2") + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", + &api.CreateRepoOption{ + Name: "my-new-repo", + }). + AddTokenAuth(writeToken) + MakeRequest(t, req, http.StatusForbidden) +} + +func TestAPIRepoDelete(t *testing.T) { + t.Run("permitted to delete user repo w/ user scope", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo2"). + AddTokenAuth(writeToken) + MakeRequest(t, req, http.StatusNoContent) + }) + + t.Run("denied to delete user repo w/ org scope", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo2"). + AddTokenAuth(writeToken) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "token does not have at least one of required scope(s): [write:user]") + }) + + t.Run("permitted to delete org repo w/ org scope", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization) + req := NewRequest(t, "DELETE", "/api/v1/repos/org3/repo3"). + AddTokenAuth(writeToken) + MakeRequest(t, req, http.StatusNoContent) + }) + + t.Run("denied to delete org repo w/ user scope", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + session := loginUser(t, "user2") + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + req := NewRequest(t, "DELETE", "/api/v1/repos/org3/repo3"). + AddTokenAuth(writeToken) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "token does not have at least one of required scope(s): [write:organization]") + }) + + t.Run("denied with repo-specific", func(t *testing.T) { + defer tests.PrepareTestEnv(t)() + // limit ourselves to write:repository -- repo-specific access tokens can't be created with write:user + repo2OnlyToken := createFineGrainedRepoAccessToken(t, "user2", + []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteRepository}, + []int64{2}, + ) + req := NewRequest(t, "DELETE", "/api/v1/repos/user2/repo2"). + AddTokenAuth(repo2OnlyToken) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "token does not have at least one of required scope(s): [write:user]") + }) +} + func TestAPIRepoTransfer(t *testing.T) { testCases := []struct { ctxUserID int64 @@ -884,6 +953,23 @@ func TestAPIRepoTransfer(t *testing.T) { _ = repo_service.DeleteRepositoryDirectly(db.DefaultContext, user, repo.ID) } +// This test verifies that a repo-specific access token with `write:repository` scope is not a sufficient to transfer a +// repository to another user. +func TestAPIRepoTransferAccessTokenResources(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + repo2OnlyToken := createFineGrainedRepoAccessToken(t, "user2", + []auth_model.AccessTokenScope{auth_model.AccessTokenScopeWriteRepository}, + []int64{2}, + ) + + req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo2/transfer", &api.TransferRepoOption{ + NewOwner: "org3", + }).AddTokenAuth(repo2OnlyToken) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "user should be an owner or a collaborator with admin write") +} + func transfer(t *testing.T) *repo_model.Repository { // create repo to move user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) @@ -972,38 +1058,93 @@ func TestAPIRejectTransfer(t *testing.T) { func TestAPIGenerateRepo(t *testing.T) { defer tests.PrepareTestEnv(t)() + templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - templateRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 44}) + // write:repository scope is always required (logically, because we're writing inside the contents of a new + // repository) but the need for write:user or write:organization depends on the target owner, so we'll test those + // combinations. - // user - repo := new(api.Repository) - req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ - Owner: user.Name, - Name: "new-repo", - Description: "test generate repo", - Private: false, - GitContent: true, - }).AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, repo) + t.Run("permitted to generate into user with user scope", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - assert.Equal(t, "new-repo", repo.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository) + repo := new(api.Repository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: user.Name, + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, repo) + assert.Equal(t, "new-repo", repo.Name) + }) - // org - req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ - Owner: "org3", - Name: "new-repo", - Description: "test generate repo", - Private: false, - GitContent: true, - }).AddTokenAuth(token) - resp = MakeRequest(t, req, http.StatusCreated) - DecodeJSON(t, resp, repo) + t.Run("denied to generate into user without user scope", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - assert.Equal(t, "new-repo", repo.Name) + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: user.Name, + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "token requires scope write:user to create a repository owned by a user") + }) + + t.Run("permitted to generate into org with org scope", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteOrganization, auth_model.AccessTokenScopeWriteRepository) + repo := new(api.Repository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: "org3", + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusCreated) + DecodeJSON(t, resp, repo) + + assert.Equal(t, "new-repo", repo.Name) + }) + + t.Run("denied to generate into org without org scope", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: "org3", + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "token requires scope write:organization to create a repository owned by a user") + }) + + t.Run("denied to generate without write:repository", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser) + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/generate", templateRepo.OwnerName, templateRepo.Name), &api.GenerateRepoOption{ + Owner: user.Name, + Name: "new-repo", + Description: "test generate repo", + Private: false, + GitContent: true, + }).AddTokenAuth(token) + resp := MakeRequest(t, req, http.StatusForbidden) + assert.Contains(t, resp.Body.String(), "token does not have at least one of required scope(s): [write:repository]") + }) } func TestAPIRepoGetReviewers(t *testing.T) { diff --git a/tests/integration/api_token_test.go b/tests/integration/api_token_test.go index f650bcbe02..2a66bc7a98 100644 --- a/tests/integration/api_token_test.go +++ b/tests/integration/api_token_test.go @@ -346,16 +346,6 @@ func TestAPIDeniesPermissionBasedOnTokenScope(t *testing.T) { }, }, }, - { - "/api/v1/repos/user1/repo1", - "DELETE", - []permission{ - { - auth_model.AccessTokenScopeCategoryRepository, - auth_model.Write, - }, - }, - }, { "/api/v1/repos/user1/repo1/branches", "GET",