diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index cb52c2c9e2058..73884f5fb0b21 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -169,7 +169,8 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle { } type ActionsConfig struct { - DisabledWorkflows []string + DisabledWorkflows []string + AccessbleFromOtherRepos bool } func (cfg *ActionsConfig) EnableWorkflow(file string) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 9945eb4949d68..82ac38ea00d95 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3754,6 +3754,12 @@ variables.creation.success = The variable "%s" has been added. variables.update.failed = Failed to edit variable. variables.update.success = The variable has been edited. +general = General +general.settings = Actions General Settings +general.actions_accessible_from_other_repositories = Accessible from repositories owned by '%s' +general.actions_accessible_from_other_repositories_desc = Workflows in other repositories that are owned by the user '%s' can access the actions and reusable workflows in this repository. Access is allowed only from private repositories. +general.actions_always_accessible_desc = The actions and workflows of a public repository are always accessible to other repositories. + [projects] deleted.display_name = Deleted Project type-1.display_name = Individual Project diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 58a2bdbab1c34..d7e036067527c 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -195,8 +195,20 @@ func httpBase(ctx *context.Context) *serviceHandler { return nil } if task.RepoID != repo.ID { - ctx.PlainText(http.StatusForbidden, "User permission denied") - return nil + actionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig() + taskRepo, err := repo_model.GetRepositoryByID(ctx, task.RepoID) + if err != nil { + ctx.ServerError("GetRepositoryByID", err) + return nil + } + if !actionsCfg.AccessbleFromOtherRepos || taskRepo.OwnerID != repo.OwnerID || !taskRepo.IsPrivate { + // See https://docs.github.com/en/actions/sharing-automations/sharing-actions-and-workflows-from-your-private-repository + // Any actions or reusable workflows stored in the private repository can be used in + // workflows defined in other private repositories owned by the same organization or user. + // Actions and reusable workflows stored in private repositories cannot be used in public repositories. + ctx.PlainText(http.StatusForbidden, "User permission denied") + return nil + } } if task.IsForkPullRequest { diff --git a/routers/web/repo/setting/actions.go b/routers/web/repo/setting/actions.go new file mode 100644 index 0000000000000..a1bfe46b99a2b --- /dev/null +++ b/routers/web/repo/setting/actions.go @@ -0,0 +1,53 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package setting + +import ( + "net/http" + + repo_model "code.gitea.io/gitea/models/repo" + unit_model "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/services/context" +) + +const tplRepoActionsGeneralSettings base.TplName = "repo/settings/actions" + +func ActionsGeneralSettings(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("actions.general") + ctx.Data["PageType"] = "general" + ctx.Data["PageIsActionsSettingsGeneral"] = true + + var accessbleFromOtherRepos bool + if !ctx.Repo.Repository.IsPrivate { + accessbleFromOtherRepos = true + } else { + actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions) + if err != nil { + ctx.ServerError("GetUnit", err) + return + } + accessbleFromOtherRepos = actionsUnit.ActionsConfig().AccessbleFromOtherRepos + } + ctx.Data["AccessibleFromOtherRepos"] = accessbleFromOtherRepos + + ctx.HTML(http.StatusOK, tplRepoActionsGeneralSettings) +} + +func ActionsGeneralSettingsPost(ctx *context.Context) { + actionsUnit, err := ctx.Repo.Repository.GetUnit(ctx, unit_model.TypeActions) + if err != nil { + ctx.ServerError("GetUnit", err) + return + } + actionsCfg := actionsUnit.ActionsConfig() + actionsCfg.AccessbleFromOtherRepos = ctx.FormBool("actions_accessible_from_other_repositories") + + if err := repo_model.UpdateRepoUnit(ctx, actionsUnit); err != nil { + ctx.ServerError("UpdateRepoUnit", err) + return + } + + ctx.Redirect(ctx.Repo.RepoLink + "/settings/actions/general") +} diff --git a/routers/web/web.go b/routers/web/web.go index b96d06ed66eb6..2995de8d95f9f 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1133,6 +1133,9 @@ func registerRoutes(m *web.Router) { addSettingsRunnersRoutes() addSettingsSecretsRoutes() addSettingsVariablesRoutes() + m.Combo("/general"). + Get(repo_setting.ActionsGeneralSettings). + Post(repo_setting.ActionsGeneralSettingsPost) }, actions.MustEnableActions) // the follow handler must be under "settings", otherwise this incomplete repo can't be accessed m.Group("/migrate", func() { diff --git a/templates/repo/settings/actions.tmpl b/templates/repo/settings/actions.tmpl index f38ab5b658412..5388de35af35e 100644 --- a/templates/repo/settings/actions.tmpl +++ b/templates/repo/settings/actions.tmpl @@ -6,6 +6,8 @@ {{template "shared/secrets/add_list" .}} {{else if eq .PageType "variables"}} {{template "shared/variables/variable_list" .}} + {{else if eq .PageType "general"}} + {{template "repo/settings/actions_general" .}} {{end}} {{template "repo/settings/layout_footer" .}} diff --git a/templates/repo/settings/actions_general.tmpl b/templates/repo/settings/actions_general.tmpl new file mode 100644 index 0000000000000..071d386b31985 --- /dev/null +++ b/templates/repo/settings/actions_general.tmpl @@ -0,0 +1,27 @@ +