Skip to content

Support actions and reusable workflows from private repos #32562

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 24 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions models/actions/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/modules/container"
"code.gitea.io/gitea/modules/log"
Expand All @@ -37,9 +38,10 @@ type ActionTask struct {
Started timeutil.TimeStamp `xorm:"index"`
Stopped timeutil.TimeStamp `xorm:"index(stopped_log_expired)"`

RepoID int64 `xorm:"index"`
OwnerID int64 `xorm:"index"`
CommitSHA string `xorm:"index"`
RepoID int64 `xorm:"index"`
Repo *repo_model.Repository `xorm:"-"`
OwnerID int64 `xorm:"index"`
CommitSHA string `xorm:"index"`
IsForkPullRequest bool

Token string `xorm:"-"`
Expand Down Expand Up @@ -143,14 +145,22 @@ func (task *ActionTask) LoadAttributes(ctx context.Context) error {
task.Steps = steps
}

return nil
return task.LoadRepository(ctx)
}

func (task *ActionTask) GenerateToken() (err error) {
task.Token, task.TokenSalt, task.TokenHash, task.TokenLastEight, err = generateSaltedToken()
return err
}

func (task *ActionTask) LoadRepository(ctx context.Context) (err error) {
if task.Repo != nil {
return nil
}
task.Repo, err = repo_model.GetRepositoryByID(ctx, task.RepoID)
return err
}

func GetTaskByID(ctx context.Context, id int64) (*ActionTask, error) {
var task ActionTask
has, err := db.GetEngine(ctx).Where("id=?", id).Get(&task)
Expand Down
19 changes: 19 additions & 0 deletions models/fixtures/action_run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,22 @@
updated: 1683636626
need_approval: 0
approved_by: 0
-
id: 793
title: "use a private action"
repo_id: 6
owner_id: 10
workflow_id: "run.yaml"
index: 189
trigger_user_id: 10
ref: "refs/heads/master"
commit_sha: "6e64b26de7ba966d01d90ecfaf5c7f14ef203e86"
event: "push"
is_fork_pull_request: 0
status: 1
started: 1683636528
stopped: 1683636626
created: 1683636108
updated: 1683636626
need_approval: 0
approved_by: 0
14 changes: 14 additions & 0 deletions models/fixtures/action_run_job.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,17 @@
status: 1
started: 1683636528
stopped: 1683636626
-
id: 194
run_id: 793
repo_id: 6
owner_id: 10
commit_sha: 6e64b26de7ba966d01d90ecfaf5c7f14ef203e86
is_fork_pull_request: 0
name: job_2
attempt: 1
job_id: job_2
task_id: 48
status: 1
started: 1683636528
stopped: 1683636626
20 changes: 20 additions & 0 deletions models/fixtures/action_task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,23 @@
log_length: 707
log_size: 90179
log_expired: 0
-
id: 49
job_id: 194
attempt: 1
runner_id: 1
status: 6 # 6 is the status code for "running"
started: 1683636528
stopped: 1683636626
repo_id: 6
owner_id: 10
commit_sha: 6e64b26de7ba966d01d90ecfaf5c7f14ef203e86
is_fork_pull_request: 0
token_hash: b8d3962425466b6709b9ac51446f93260c54afe8e7b6d3686e34f991fb8a8953822b0deed86fe41a103f34bc48dbc478422b
token_salt: ERxJGHvg3I
token_last_eight: 182199eb
log_filename: collaborative-owner-test/1a/49.log
log_in_storage: 1
log_length: 707
log_size: 90179
log_expired: 0
7 changes: 7 additions & 0 deletions models/fixtures/repo_unit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -733,3 +733,10 @@
type: 3
config: "{\"IgnoreWhitespaceConflicts\":false,\"AllowMerge\":true,\"AllowRebase\":true,\"AllowRebaseMerge\":true,\"AllowSquash\":true}"
created_unix: 946684810

-
id: 111
repo_id: 3
type: 10
config: "{}"
created_unix: 946684810
17 changes: 17 additions & 0 deletions models/repo/repo_unit.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ func (cfg *PullRequestsConfig) GetDefaultMergeStyle() MergeStyle {

type ActionsConfig struct {
DisabledWorkflows []string
// CollaborativeOwnerIDs is a list of owner IDs used to share actions from private repos.
// Only workflows from the private repos whose owners are in CollaborativeOwnerIDs can access the current repo's actions.
CollaborativeOwnerIDs []int64
}

func (cfg *ActionsConfig) EnableWorkflow(file string) {
Expand All @@ -194,6 +197,20 @@ func (cfg *ActionsConfig) DisableWorkflow(file string) {
cfg.DisabledWorkflows = append(cfg.DisabledWorkflows, file)
}

func (cfg *ActionsConfig) AddCollaborativeOwner(ownerID int64) {
if !slices.Contains(cfg.CollaborativeOwnerIDs, ownerID) {
cfg.CollaborativeOwnerIDs = append(cfg.CollaborativeOwnerIDs, ownerID)
}
}

func (cfg *ActionsConfig) RemoveCollaborativeOwner(ownerID int64) {
cfg.CollaborativeOwnerIDs = util.SliceRemoveAll(cfg.CollaborativeOwnerIDs, ownerID)
}

func (cfg *ActionsConfig) IsCollaborativeOwner(ownerID int64) bool {
return slices.Contains(cfg.CollaborativeOwnerIDs, ownerID)
}

// FromDB fills up a ActionsConfig from serialized format.
func (cfg *ActionsConfig) FromDB(bs []byte) error {
return json.UnmarshalHandleDoubleEncode(bs, &cfg)
Expand Down
10 changes: 6 additions & 4 deletions models/user/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package user
import (
"context"
"fmt"
"slices"
"strings"

"code.gitea.io/gitea/models/db"
Expand All @@ -22,7 +23,7 @@ type SearchUserOptions struct {
db.ListOptions

Keyword string
Type UserType
Types []UserType
UID int64
LoginName string // this option should be used only for admin user
SourceID int64 // this option should be used only for admin user
Expand All @@ -45,15 +46,16 @@ type SearchUserOptions struct {

func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
var cond builder.Cond
cond = builder.Eq{"type": opts.Type}
cond = builder.In("type", opts.Types)
if opts.IncludeReserved {
if opts.Type == UserTypeIndividual {
if slices.Contains(opts.Types, UserTypeIndividual) {
cond = cond.Or(builder.Eq{"type": UserTypeUserReserved}).Or(
builder.Eq{"type": UserTypeBot},
).Or(
builder.Eq{"type": UserTypeRemoteUser},
)
} else if opts.Type == UserTypeOrganization {
}
if slices.Contains(opts.Types, UserTypeOrganization) {
cond = cond.Or(builder.Eq{"type": UserTypeOrganizationReserved})
}
}
Expand Down
12 changes: 12 additions & 0 deletions models/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -1317,3 +1317,15 @@ func DisabledFeaturesWithLoginType(user *User) *container.Set[string] {
}
return &setting.Admin.UserDisabledFeatures
}

// GetUserOrOrgIDByName returns the id for a user or an org by name
func GetUserOrOrgIDByName(ctx context.Context, name string) (int64, error) {
var id int64
has, err := db.GetEngine(ctx).Table("user").Where("name = ?", name).Cols("id").Get(&id)
if err != nil {
return 0, err
} else if !has {
return 0, fmt.Errorf("user or org with name %s: %w", name, util.ErrNotExist)
}
return id, nil
}
4 changes: 2 additions & 2 deletions models/user/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func TestSearchUsers(t *testing.T) {

// test orgs
testOrgSuccess := func(opts *user_model.SearchUserOptions, expectedOrgIDs []int64) {
opts.Type = user_model.UserTypeOrganization
opts.Types = []user_model.UserType{user_model.UserTypeOrganization}
testSuccess(opts, expectedOrgIDs)
}

Expand All @@ -100,7 +100,7 @@ func TestSearchUsers(t *testing.T) {

// test users
testUserSuccess := func(opts *user_model.SearchUserOptions, expectedUserIDs []int64) {
opts.Type = user_model.UserTypeIndividual
opts.Types = []user_model.UserType{user_model.UserTypeIndividual}
testSuccess(opts, expectedUserIDs)
}

Expand Down
9 changes: 9 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3755,6 +3755,15 @@ 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.collaborative_owners_management = Collaborative Owners Management
general.collaborative_owners_management_help = A collaborative owner is a user or an organization whose private repository has access to the actions and workflows of this repository.
general.add_collaborative_owner = Add Collaborative Owner
general.collaborative_owner_not_exist = The collaborative owner does not exist.
general.remove_collaborative_owner = Remove Collaborative Owner
general.remove_collaborative_owner_desc = Removing a collaborative owner will prevent the repositories of the owner from accessing the actions in this repository. Continue?
general.collaborative_owner_not_required = The actions and workflows of a public repository are always accessible to other repositories. You do not need to specify collaborative owners.

[projects]
deleted.display_name = Deleted Project
type-1.display_name = Individual Project
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/admin/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func GetAllOrgs(ctx *context.APIContext) {

users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
OrderBy: db.SearchOrderByAlphabetically,
ListOptions: listOptions,
Visible: []api.VisibleType{api.VisibleTypePublic, api.VisibleTypeLimited, api.VisibleTypePrivate},
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/admin/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ func SearchUsers(ctx *context.APIContext) {

users, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
LoginName: ctx.FormTrim("login_name"),
SourceID: ctx.FormInt64("source_id"),
OrderBy: db.SearchOrderByAlphabetically,
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/org/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func GetAll(ctx *context.APIContext) {
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
ListOptions: listOptions,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
OrderBy: db.SearchOrderByAlphabetically,
Visible: vMode,
})
Expand Down
2 changes: 1 addition & 1 deletion routers/api/v1/user/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func Search(ctx *context.APIContext) {
Actor: ctx.Doer,
Keyword: ctx.FormTrim("q"),
UID: uid,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
SearchByEmail: true,
Visible: visible,
ListOptions: listOptions,
Expand Down
2 changes: 1 addition & 1 deletion routers/web/admin/orgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func Organizations(ctx *context.Context) {

explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
IncludeReserved: true, // administrator needs to list all accounts include reserved
ListOptions: db.ListOptions{
PageSize: setting.UI.Admin.OrgPagingNum,
Expand Down
2 changes: 1 addition & 1 deletion routers/web/admin/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func Users(ctx *context.Context) {

explore.RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
ListOptions: db.ListOptions{
PageSize: setting.UI.Admin.UserPagingNum,
},
Expand Down
2 changes: 1 addition & 1 deletion routers/web/explore/org.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func Organizations(ctx *context.Context) {

RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeOrganization,
Types: []user_model.UserType{user_model.UserTypeOrganization},
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
Visible: visibleTypes,

Expand Down
2 changes: 1 addition & 1 deletion routers/web/explore/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func Users(ctx *context.Context) {

RenderUserSearch(ctx, &user_model.SearchUserOptions{
Actor: ctx.Doer,
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
ListOptions: db.ListOptions{PageSize: setting.UI.ExplorePagingNum},
IsActive: optional.Some(true),
Visible: []structs.VisibleType{structs.VisibleTypePublic, structs.VisibleTypeLimited, structs.VisibleTypePrivate},
Expand Down
2 changes: 1 addition & 1 deletion routers/web/home.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func HomeSitemap(ctx *context.Context) {
m := sitemap.NewSitemapIndex()
if !setting.Service.Explore.DisableUsersPage {
_, cnt, err := user_model.SearchUsers(ctx, &user_model.SearchUserOptions{
Type: user_model.UserTypeIndividual,
Types: []user_model.UserType{user_model.UserTypeIndividual},
ListOptions: db.ListOptions{PageSize: 1},
IsActive: optional.Some(true),
Visible: []structs.VisibleType{structs.VisibleTypePublic},
Expand Down
13 changes: 11 additions & 2 deletions routers/web/repo/githttp.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,8 +195,17 @@ func httpBase(ctx *context.Context) *serviceHandler {
return nil
}
if task.RepoID != repo.ID {
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
if err := task.LoadRepository(ctx); err != nil {
ctx.ServerError("LoadRepository", err)
return nil
}
actionsCfg := repo.MustGetUnit(ctx, unit.TypeActions).ActionsConfig()
if !actionsCfg.IsCollaborativeOwner(task.Repo.OwnerID) || !task.Repo.IsPrivate {
// The task repo can access the current repo only if the task repo is private and
// the owner of the task repo is a collaborative owner of the current repo.
ctx.PlainText(http.StatusForbidden, "User permission denied")
return nil
}
}

if task.IsForkPullRequest {
Expand Down
Loading
Loading