diff --git a/models/repo/repo_unit.go b/models/repo/repo_unit.go index a04023462a..353ec5402a 100644 --- a/models/repo/repo_unit.go +++ b/models/repo/repo_unit.go @@ -176,6 +176,8 @@ const ( ActionsTokenPermissionModePermissive ActionsTokenPermissionMode = "permissive" // ActionsTokenPermissionModeRestricted - read access by default ActionsTokenPermissionModeRestricted ActionsTokenPermissionMode = "restricted" + // ActionsTokenPermissionModeCustom - custom permissions defined by MaxTokenPermissions + ActionsTokenPermissionModeCustom ActionsTokenPermissionMode = "custom" ) // ActionsTokenPermissions defines the permissions for different repository units @@ -272,6 +274,10 @@ type ActionsConfig struct { MaxTokenPermissions *ActionsTokenPermissions `json:"max_token_permissions,omitempty"` // AllowCrossRepoAccess indicates if actions in this repo/org can access other repos in the same org AllowCrossRepoAccess bool `json:"allow_cross_repo_access,omitempty"` + // AllowedCrossRepoIDs is a list of specific repo IDs that can be accessed cross-repo (empty means all if AllowCrossRepoAccess is true) + AllowedCrossRepoIDs []int64 `json:"allowed_cross_repo_ids,omitempty"` + // FollowOrgConfig indicates if this repository should follow the organization-level configuration + FollowOrgConfig bool `json:"follow_org_config,omitempty"` } func (cfg *ActionsConfig) EnableWorkflow(file string) { @@ -361,6 +367,20 @@ func (cfg *ActionsConfig) ClampPermissions(perms ActionsTokenPermissions) Action } } +// IsRepoAllowedCrossAccess checks if a specific repo is allowed for cross-repo access +// Returns true if AllowCrossRepoAccess is enabled AND (AllowedCrossRepoIDs is empty OR repoID is in the list) +func (cfg *ActionsConfig) IsRepoAllowedCrossAccess(repoID int64) bool { + if !cfg.AllowCrossRepoAccess { + return false + } + // If no specific repos are configured, allow all + if len(cfg.AllowedCrossRepoIDs) == 0 { + return true + } + // Check if repo is in the allowed list + return slices.Contains(cfg.AllowedCrossRepoIDs, repoID) +} + // FromDB fills up a ActionsConfig from serialized format. func (cfg *ActionsConfig) FromDB(bs []byte) error { return json.UnmarshalHandleDoubleEncode(bs, &cfg) diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 64fc9d09eb..144360d4b7 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3740,8 +3740,12 @@ "actions.general.token_permissions.none": "None", "actions.general.token_permissions.cross_repo": "Allow Cross-Repository Access", "actions.general.token_permissions.cross_repo_desc": "Control whether the token can access other repositories and packages within this organization.", + "actions.general.token_permissions.cross_repo_all": "All repositories in this organization", + "actions.general.token_permissions.cross_repo_selected": "Selected repositories only", "actions.general.token_permissions.allowed_repos": "Allowed Repositories", "actions.general.token_permissions.add_repo": "Add Repository", + "actions.general.token_permissions.follow_org": "Follow organization-level configuration", + "actions.general.token_permissions.follow_org_desc": "Use the Actions settings configured at the organization level instead of repository-specific settings.", "all_repositories": "All Repositories", "specific_repositories": "Specific Repositories" } diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index e084dc13e1..49ef440d03 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -105,21 +105,26 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { packageRepoID = ctx.Package.Descriptor.Package.RepoID } + // If package is not linked to any repo (org-level package), deny access from Actions + // Actions tokens should only access packages linked to repos + if packageRepoID == 0 { + ctx.HTTPError(http.StatusForbidden, "reqPackageAccess", "Actions tokens cannot access packages not linked to a repository") + return + } + if task.RepoID != packageRepoID { - // Cross-repository access - check org policy first + // Cross-repository access - check org policy cfg, err := actions_model.GetOrgActionsConfig(ctx, ctx.Package.Owner.ID) if err != nil { log.Error("GetOrgActionsConfig: %v", err) ctx.HTTPError(http.StatusInternalServerError, "GetOrgActionsConfig", err.Error()) return } - if !cfg.AllowCrossRepoAccess { - ctx.HTTPError(http.StatusForbidden, "reqPackageAccess", "cross-repository package access is disabled") + // Use selective cross-repo access check + if !cfg.IsRepoAllowedCrossAccess(packageRepoID) { + ctx.HTTPError(http.StatusForbidden, "reqPackageAccess", "cross-repository package access is not allowed for this repository") return } - - // Cross-repo is enabled. For org-level packages (RepoID=0), allow access. - // For repo-linked packages, allow read access (fallthrough to permission check below). } } } diff --git a/routers/web/org/setting/actions.go b/routers/web/org/setting/actions.go index 44c6b58bda..475a70d724 100644 --- a/routers/web/org/setting/actions.go +++ b/routers/web/org/setting/actions.go @@ -33,9 +33,11 @@ func ActionsGeneral(ctx *context.Context) { ctx.Data["TokenPermissionMode"] = actionsCfg.GetTokenPermissionMode() ctx.Data["TokenPermissionModePermissive"] = repo_model.ActionsTokenPermissionModePermissive ctx.Data["TokenPermissionModeRestricted"] = repo_model.ActionsTokenPermissionModeRestricted + ctx.Data["TokenPermissionModeCustom"] = repo_model.ActionsTokenPermissionModeCustom ctx.Data["MaxTokenPermissions"] = actionsCfg.GetMaxTokenPermissions() ctx.Data["AllowCrossRepoAccess"] = actionsCfg.AllowCrossRepoAccess + ctx.Data["HasSelectedRepos"] = len(actionsCfg.AllowedCrossRepoIDs) > 0 ctx.HTML(http.StatusOK, tplSettingsActionsGeneral) } @@ -55,7 +57,8 @@ func ActionsGeneralPost(ctx *context.Context) { // Update Token Permission Mode permissionMode := repo_model.ActionsTokenPermissionMode(ctx.FormString("token_permission_mode")) if permissionMode == repo_model.ActionsTokenPermissionModeRestricted || - permissionMode == repo_model.ActionsTokenPermissionModePermissive { + permissionMode == repo_model.ActionsTokenPermissionModePermissive || + permissionMode == repo_model.ActionsTokenPermissionModeCustom { actionsCfg.TokenPermissionMode = permissionMode } @@ -81,8 +84,19 @@ func ActionsGeneralPost(ctx *context.Context) { Wiki: parseMaxPerm("wiki"), } - // Update Cross-Repo Access - actionsCfg.AllowCrossRepoAccess = ctx.FormBool("allow_cross_repo_access") + // Update Cross-Repo Access Mode + crossRepoMode := ctx.FormString("cross_repo_mode") + switch crossRepoMode { + case "none": + actionsCfg.AllowCrossRepoAccess = false + actionsCfg.AllowedCrossRepoIDs = nil + case "all": + actionsCfg.AllowCrossRepoAccess = true + actionsCfg.AllowedCrossRepoIDs = nil + case "selected": + actionsCfg.AllowCrossRepoAccess = true + // Keep existing AllowedCrossRepoIDs, will be updated by separate API + } if err := actions_model.SetOrgActionsConfig(ctx, ctx.Org.Organization.AsUser().ID, actionsCfg); err != nil { ctx.ServerError("SetOrgActionsConfig", err) diff --git a/routers/web/repo/setting/actions.go b/routers/web/repo/setting/actions.go index a9b4e3761c..0ba12e9f30 100644 --- a/routers/web/repo/setting/actions.go +++ b/routers/web/repo/setting/actions.go @@ -41,8 +41,13 @@ func ActionsGeneralSettings(ctx *context.Context) { ctx.Data["TokenPermissionMode"] = actionsCfg.GetTokenPermissionMode() ctx.Data["TokenPermissionModePermissive"] = repo_model.ActionsTokenPermissionModePermissive ctx.Data["TokenPermissionModeRestricted"] = repo_model.ActionsTokenPermissionModeRestricted + ctx.Data["TokenPermissionModeCustom"] = repo_model.ActionsTokenPermissionModeCustom ctx.Data["MaxTokenPermissions"] = actionsCfg.GetMaxTokenPermissions() + // Follow org config (only for repos in orgs) + ctx.Data["IsInOrg"] = ctx.Repo.Repository.Owner.IsOrganization() + ctx.Data["FollowOrgConfig"] = actionsCfg.FollowOrgConfig + if ctx.Repo.Repository.IsPrivate { collaborativeOwnerIDs := actionsCfg.CollaborativeOwnerIDs collaborativeOwners, err := user_model.GetUsersByIDs(ctx, collaborativeOwnerIDs) @@ -141,15 +146,21 @@ func UpdateTokenPermissions(ctx *context.Context) { actionsCfg := actionsUnit.ActionsConfig() - // Update permission mode - permissionMode := repo_model.ActionsTokenPermissionMode(ctx.FormString("token_permission_mode")) - if permissionMode == repo_model.ActionsTokenPermissionModeRestricted || - permissionMode == repo_model.ActionsTokenPermissionModePermissive { - actionsCfg.TokenPermissionMode = permissionMode - } else { - ctx.Flash.Error("Invalid token permission mode") - ctx.Redirect(redirectURL) - return + // Update Follow Org Config (for repos in orgs) + actionsCfg.FollowOrgConfig = ctx.FormBool("follow_org_config") + + // Update permission mode (only if not following org config) + if !actionsCfg.FollowOrgConfig { + permissionMode := repo_model.ActionsTokenPermissionMode(ctx.FormString("token_permission_mode")) + if permissionMode == repo_model.ActionsTokenPermissionModeRestricted || + permissionMode == repo_model.ActionsTokenPermissionModePermissive || + permissionMode == repo_model.ActionsTokenPermissionModeCustom { + actionsCfg.TokenPermissionMode = permissionMode + } else { + ctx.Flash.Error("Invalid token permission mode") + ctx.Redirect(redirectURL) + return + } } // Update Maximum Permissions (radio buttons: none/read/write) diff --git a/templates/org/settings/actions_general.tmpl b/templates/org/settings/actions_general.tmpl index e98f895f9b..871ddcc4b2 100644 --- a/templates/org/settings/actions_general.tmpl +++ b/templates/org/settings/actions_general.tmpl @@ -8,12 +8,30 @@ {{.CsrfTokenHtml}} -
{{ctx.Locale.Tr "actions.general.token_permissions.cross_repo_desc"}}
+ +{{ctx.Locale.Tr "actions.general.token_permissions.cross_repo_desc"}}
{{ctx.Locale.Tr "actions.general.token_permissions.permissive.description"}}
+{{ctx.Locale.Tr "actions.general.token_permissions.mode.permissive.desc"}}
{{ctx.Locale.Tr "actions.general.token_permissions.restricted.description"}}
+{{ctx.Locale.Tr "actions.general.token_permissions.mode.restricted.desc"}}
+{{ctx.Locale.Tr "actions.general.token_permissions.mode.custom.desc"}}
{{ctx.Locale.Tr "actions.general.token_permissions.maximum.description"}}
+{{ctx.Locale.Tr "actions.general.token_permissions.max_permissions.desc"}}
+| {{ctx.Locale.Tr "units.unit"}} | diff --git a/templates/repo/settings/actions_general.tmpl b/templates/repo/settings/actions_general.tmpl index 920dfd0646..962fc8a8b0 100644 --- a/templates/repo/settings/actions_general.tmpl +++ b/templates/repo/settings/actions_general.tmpl @@ -32,22 +32,42 @@
|---|