Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion models/actions/token_permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func ComputeTaskTokenPermissions(ctx context.Context, task *ActionTask, targetRe
isSameRepo := task.Job.RepoID == targetRepo.ID
restrictCrossRepoAccess := task.IsForkPullRequest || !isSameRepo
if restrictCrossRepoAccess {
effectivePerms = repo_model.ClampActionsTokenPermissions(effectivePerms, repo_model.MakeRestrictedPermissions())
effectivePerms = repo_model.ClampActionsTokenPermissions(effectivePerms, repo_model.MakeCrossRepoAccessPermissions())
}

return effectivePerms, nil
Expand Down
21 changes: 21 additions & 0 deletions models/perm/access/actions_repo_permission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,27 @@ func TestGetActionsUserRepoPermission(t *testing.T) {
require.NoError(t, repo_model.UpdateRepoUnitConfig(ctx, repo15ActionsUnit))
})

t.Run("ForkPR_SameRepo_CanReadPullRequests", func(t *testing.T) {
// task.RepoID == repo.ID here: the workflow still runs against its own base repo.
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
require.Equal(t, repo2.ID, task53.RepoID)

task53.IsForkPullRequest = true
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))

perm, err := GetActionsUserRepoPermission(ctx, repo2, actionsUser, task53.ID)
require.NoError(t, err)

assert.True(t, perm.CanRead(unit.TypePullRequests))
assert.True(t, perm.CanRead(unit.TypeIssues))
assert.False(t, perm.CanWrite(unit.TypePullRequests))
assert.False(t, perm.CanWrite(unit.TypeIssues))

// Restore state for subsequent subtests.
task53.IsForkPullRequest = false
require.NoError(t, actions_model.UpdateTask(ctx, task53, "is_fork_pull_request"))
})

t.Run("Inheritance_And_Clamping", func(t *testing.T) {
task53 := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 53})
task53.IsForkPullRequest = false
Expand Down
6 changes: 6 additions & 0 deletions models/repo/repo_unit_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ func MakeRestrictedPermissions() ActionsTokenPermissions {
return ret
}

// MakeCrossRepoAccessPermissions returns the permission ceiling applied to Actions tokens for
// fork pull requests and cross-repository access: read-only across every unit.
func MakeCrossRepoAccessPermissions() ActionsTokenPermissions {
return MakeActionsTokenPermissions(perm.AccessModeRead)
}

type ActionsConfig struct {
DisabledWorkflows []string
// DisabledScopedWorkflows maps a scoped workflow's source repository ID to the entry names opted out of in this repository.
Expand Down
10 changes: 10 additions & 0 deletions tests/integration/actions_job_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,16 @@ func TestActionsJobTokenPermissiveAccess(t *testing.T) {
resp := MakeRequest(t, req, NoExpectedStatus)
assertRespCodeForSuccess(t, resp, false)
})

// Restricted-mode tokens deny pull request access by design (contents/packages/releases
// read only), so this only applies when the declared mode isn't Restricted.
if tt.isFork && tt.repoPermMode != repo_model.ActionsTokenPermissionModeRestricted {
t.Run("ReadPullRequests", func(t *testing.T) {
req := NewRequest(t, "GET", "/api/v1/repos/"+repo.FullName()+"/pulls").AddTokenAuth(task.Token)
resp := MakeRequest(t, req, NoExpectedStatus)
assertRespCodeForSuccess(t, resp, true)
})
}
})
}
})
Expand Down
Loading