fix: disable actions endpoints of repository if actions are disabled (#10726)
Some checks are pending
Integration tests for the release process / release-simulation (push) Waiting to run
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions

Some HTTP API endpoints related to Actions, like `/api/v1/repos/{owner}/{repository}/actions/runners`, were not disabled if Actions had been disabled on a repository. With this change, all endpoints related to Actions will be disabled if Actions are disabled.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
  - [ ] in their respective `*_test.go` for unit tests.
  - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
  - [ ] in `web_src/js/*.test.js` if it can be unit tested.
  - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [x] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [ ] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10726
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
Co-committed-by: Andreas Ahlenstorf <andreas@ahlenstorf.ch>
This commit is contained in:
Andreas Ahlenstorf 2026-01-10 16:40:00 +01:00 committed by Mathieu Fenniak
parent 6eed310ae6
commit ba2a6fbc41
4 changed files with 92 additions and 4 deletions

View file

@ -436,9 +436,16 @@ func reqSiteAdmin() func(ctx *context.APIContext) {
}
}
// reqOwner user should be the owner of the repo or site admin.
func reqOwner() func(ctx *context.APIContext) {
// reqOwner requires that the current user is either the owner of the repository or an administrator. If one or more
// unitTypes are given, it also requires that at least one the respective unitTypes is enabled.
func reqOwner(unitTypes ...unit.Type) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if len(unitTypes) > 0 && !slices.ContainsFunc(unitTypes, func(unitType unit.Type) bool {
return ctx.Repo.Repository.UnitEnabled(ctx, unitType)
}) {
ctx.NotFound()
return
}
if !ctx.Repo.IsOwner() && !ctx.IsUserSiteAdmin() {
ctx.Error(http.StatusForbidden, "reqOwner", "user should be the owner of the repo")
return
@ -466,7 +473,8 @@ func reqAdmin() func(ctx *context.APIContext) {
}
}
// reqRepoWriter user should have a permission to write to a repo, or be a site admin
// reqRepoWriter requires that the current user has permission to write to a repository or that it is an administrator.
// One or more unitTypes have to be specified, and at least one of them has to be enabled.
func reqRepoWriter(unitTypes ...unit.Type) func(ctx *context.APIContext) {
return func(ctx *context.APIContext) {
if !slices.ContainsFunc(unitTypes, func(unitType unit.Type) bool {
@ -1145,7 +1153,7 @@ func Routes() *web.Route {
}, reqToken())
addActionsRoutes(
m,
reqOwner(),
reqOwner(unit.TypeActions),
repo.NewAction(),
)
m.Group("/hooks/git", func() {

View file

@ -13,6 +13,7 @@ import (
actions_model "forgejo.org/models/actions"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
unit_model "forgejo.org/models/unit"
"forgejo.org/models/unittest"
@ -20,6 +21,7 @@ import (
api "forgejo.org/modules/structs"
"forgejo.org/modules/webhook"
"forgejo.org/routers/api/v1/shared"
repo_service "forgejo.org/services/repository"
files_service "forgejo.org/services/repository/files"
"forgejo.org/tests"
@ -545,4 +547,25 @@ func TestAPIRepoActionsRunnerOperations(t *testing.T) {
assert.Equal(t, "token does not have at least one of required scope(s): [write:repository]", errorMessage.Message)
})
t.Run("Endpoints disabled if Actions disabled", func(t *testing.T) {
repository, _, cleanUp := tests.CreateDeclarativeRepo(t, user2, "no-actions",
[]unit_model.Type{unit_model.TypeCode, unit_model.TypeActions}, []unit_model.Type{}, nil)
defer cleanUp()
requestURL := fmt.Sprintf("/api/v1/repos/%s/actions/runners", repository.FullName())
request := NewRequest(t, "GET", requestURL)
request.AddTokenAuth(readToken)
MakeRequest(t, request, http.StatusOK)
enabledUnits := []repo_model.RepoUnit{{RepoID: repository.ID, Type: unit_model.TypeCode}}
disabledUnits := []unit_model.Type{unit_model.TypeActions}
err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repository, enabledUnits, disabledUnits)
require.NoError(t, err)
request = NewRequest(t, "GET", requestURL)
request.AddTokenAuth(readToken)
MakeRequest(t, request, http.StatusNotFound)
})
}

View file

@ -9,11 +9,16 @@ import (
"testing"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
unit_model "forgejo.org/models/unit"
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
api "forgejo.org/modules/structs"
repo_service "forgejo.org/services/repository"
"forgejo.org/tests"
"github.com/stretchr/testify/require"
)
func TestAPIRepoSecrets(t *testing.T) {
@ -109,4 +114,25 @@ func TestAPIRepoSecrets(t *testing.T) {
AddTokenAuth(token)
MakeRequest(t, req, http.StatusBadRequest)
})
t.Run("Endpoints disabled if Actions disabled", func(t *testing.T) {
repository, _, cleanUp := tests.CreateDeclarativeRepo(t, user, "no-actions",
[]unit_model.Type{unit_model.TypeCode, unit_model.TypeActions}, []unit_model.Type{}, nil)
defer cleanUp()
getURL := fmt.Sprintf("/api/v1/repos/%s/actions/secrets", repository.FullName())
getRequest := NewRequest(t, "GET", getURL)
getRequest.AddTokenAuth(token)
MakeRequest(t, getRequest, http.StatusOK)
enabledUnits := []repo_model.RepoUnit{{RepoID: repository.ID, Type: unit_model.TypeCode}}
disabledUnits := []unit_model.Type{unit_model.TypeActions}
err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repository, enabledUnits, disabledUnits)
require.NoError(t, err)
getRequest = NewRequest(t, "GET", getURL)
getRequest.AddTokenAuth(token)
MakeRequest(t, getRequest, http.StatusNotFound)
})
}

View file

@ -9,13 +9,17 @@ import (
"testing"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
repo_model "forgejo.org/models/repo"
unit_model "forgejo.org/models/unit"
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
api "forgejo.org/modules/structs"
repo_service "forgejo.org/services/repository"
"forgejo.org/tests"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAPIRepoVariablesTestCreateRepositoryVariable(t *testing.T) {
@ -234,3 +238,30 @@ func TestAPIRepoVariablesGetAllRepositoryVariables(t *testing.T) {
assert.Equal(t, "SECOND", actionVariables[1].Name)
assert.Equal(t, "Dolor sit amet", actionVariables[1].Data)
}
func TestAPIRepoVariablesEndpointsDisabledIfActionsDisabled(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
session := loginUser(t, user2.Name)
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
repository, _, cleanUp := tests.CreateDeclarativeRepo(t, user2, "no-actions",
[]unit_model.Type{unit_model.TypeCode, unit_model.TypeActions}, []unit_model.Type{}, nil)
defer cleanUp()
getURL := fmt.Sprintf("/api/v1/repos/%s/actions/variables", repository.FullName())
getRequest := NewRequest(t, "GET", getURL)
getRequest.AddTokenAuth(token)
MakeRequest(t, getRequest, http.StatusOK)
enabledUnits := []repo_model.RepoUnit{{RepoID: repository.ID, Type: unit_model.TypeCode}}
disabledUnits := []unit_model.Type{unit_model.TypeActions}
err := repo_service.UpdateRepositoryUnits(db.DefaultContext, repository, enabledUnits, disabledUnits)
require.NoError(t, err)
getRequest = NewRequest(t, "GET", getURL)
getRequest.AddTokenAuth(token)
MakeRequest(t, getRequest, http.StatusNotFound)
}