mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-03-13 07:52:42 +00:00
Add allow list for approvals (#4768)
Co-authored-by: Anbraten <6918444+anbraten@users.noreply.github.com> Co-authored-by: Robert Kaussow <xoxys@rknet.org> Co-authored-by: Robert Kaussow <mail@thegeeklab.de>
This commit is contained in:
parent
ee0e5fb47f
commit
8e99551d18
8 changed files with 86 additions and 2 deletions
|
@ -5054,6 +5054,12 @@ const docTemplate = `{
|
|||
"allow_pr": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"approval_allowed_users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"avatar_url": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -5135,6 +5141,12 @@ const docTemplate = `{
|
|||
"allow_pr": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"approval_allowed_users": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"cancel_previous_pipeline_events": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
|
|
@ -259,6 +259,9 @@ func PatchRepo(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
}
|
||||
if in.ApprovalAllowedUsers != nil {
|
||||
repo.ApprovalAllowedUsers = *in.ApprovalAllowedUsers
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
repo.Timeout = *in.Timeout
|
||||
}
|
||||
|
|
|
@ -63,6 +63,7 @@ type Repo struct {
|
|||
IsSCMPrivate bool `json:"private" xorm:"private"`
|
||||
Trusted TrustedConfiguration `json:"trusted" xorm:"json 'trusted'"`
|
||||
RequireApproval ApprovalMode `json:"require_approval" xorm:"varchar(50) require_approval"`
|
||||
ApprovalAllowedUsers []string `json:"approval_allowed_users" xorm:"json approval_allowed_users"`
|
||||
IsActive bool `json:"active" xorm:"active"`
|
||||
AllowPull bool `json:"allow_pr" xorm:"allow_pr"`
|
||||
AllowDeploy bool `json:"allow_deploy" xorm:"allow_deploy"`
|
||||
|
@ -129,6 +130,7 @@ func (r *Repo) Update(from *Repo) {
|
|||
type RepoPatch struct {
|
||||
Config *string `json:"config_file,omitempty"`
|
||||
RequireApproval *string `json:"require_approval,omitempty"`
|
||||
ApprovalAllowedUsers *[]string `json:"approval_allowed_users,omitempty"`
|
||||
Timeout *int64 `json:"timeout,omitempty"`
|
||||
Visibility *string `json:"visibility,omitempty"`
|
||||
AllowPull *bool `json:"allow_pr,omitempty"`
|
||||
|
|
|
@ -14,7 +14,11 @@
|
|||
|
||||
package pipeline
|
||||
|
||||
import "go.woodpecker-ci.org/woodpecker/v3/server/model"
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/model"
|
||||
)
|
||||
|
||||
func setApprovalState(repo *model.Repo, pipeline *model.Pipeline) {
|
||||
if !needsApproval(repo, pipeline) {
|
||||
|
@ -31,6 +35,12 @@ func needsApproval(repo *model.Repo, pipeline *model.Pipeline) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// skip if user is allowed
|
||||
// It's enough to check the username as the repo matches the forge of the pipeline already (no username clashes from different forges possible)
|
||||
if slices.Contains(repo.ApprovalAllowedUsers, pipeline.Author) {
|
||||
return false
|
||||
}
|
||||
|
||||
switch repo.RequireApproval {
|
||||
// repository allows all events without approval
|
||||
case model.RequireApprovalNone:
|
||||
|
|
|
@ -69,6 +69,18 @@ func TestSetGatedState(t *testing.T) {
|
|||
},
|
||||
expectBlocked: true,
|
||||
},
|
||||
{
|
||||
name: "require approval for everything with allowed user",
|
||||
repo: &model.Repo{
|
||||
RequireApproval: model.RequireApprovalAllEvents,
|
||||
ApprovalAllowedUsers: []string{"user"},
|
||||
},
|
||||
pipeline: &model.Pipeline{
|
||||
Event: model.EventPush,
|
||||
Author: "user",
|
||||
},
|
||||
expectBlocked: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
|
|
@ -509,7 +509,11 @@
|
|||
"none_desc": "Every event triggers pipelines, including pull requests. This setting can be dangerous and is only recommended for private instances.",
|
||||
"forks": "Pull request from forked repository",
|
||||
"pull_requests": "All pull requests",
|
||||
"all_events": "All events from forge"
|
||||
"all_events": "All events from forge",
|
||||
"allowed_users": {
|
||||
"allowed_users": "Allowed users",
|
||||
"desc": "Pipelines created by the listed users never require approval."
|
||||
}
|
||||
},
|
||||
"all_repositories": "All repositories",
|
||||
"no_search_results": "No results found"
|
||||
|
|
|
@ -73,6 +73,8 @@ export interface Repo {
|
|||
|
||||
require_approval: RepoRequireApproval;
|
||||
|
||||
approval_allowed_users: string[];
|
||||
|
||||
// Events that will cancel running pipelines before starting a new one
|
||||
cancel_previous_pipeline_events: string[];
|
||||
|
||||
|
@ -101,6 +103,7 @@ export type RepoSettings = Pick<
|
|||
| 'visibility'
|
||||
| 'trusted'
|
||||
| 'require_approval'
|
||||
| 'approval_allowed_users'
|
||||
| 'allow_pr'
|
||||
| 'allow_deploy'
|
||||
| 'cancel_previous_pipeline_events'
|
||||
|
|
|
@ -88,6 +88,27 @@
|
|||
</template>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
v-if="repoSettings.require_approval !== RepoRequireApproval.None"
|
||||
:label="$t('require_approval.allowed_users.allowed_users')"
|
||||
>
|
||||
<template #default="{ id }">
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-for="allowedUser in repoSettings.approval_allowed_users" :key="allowedUser" class="flex gap-2">
|
||||
<TextField :id="id" :model-value="allowedUser" disabled />
|
||||
<Button type="button" color="gray" start-icon="trash" @click="removeUser(allowedUser)" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<TextField :id="id" v-model="newUser" @keydown.enter.prevent="addNewUser" />
|
||||
<Button type="button" color="gray" start-icon="plus" @click="addNewUser" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ $t('require_approval.allowed_users.desc') }}
|
||||
</template>
|
||||
</InputField>
|
||||
|
||||
<InputField docs-url="docs/usage/project-settings#project-visibility" :label="$t('repo.visibility.visibility')">
|
||||
<RadioField v-model="repoSettings.visibility" :options="projectVisibilityOptions" />
|
||||
</InputField>
|
||||
|
@ -191,6 +212,7 @@ function loadRepoSettings() {
|
|||
visibility: repo.value.visibility,
|
||||
require_approval: repo.value.require_approval,
|
||||
trusted: repo.value.trusted,
|
||||
approval_allowed_users: repo.value.approval_allowed_users || [],
|
||||
allow_pr: repo.value.allow_pr,
|
||||
allow_deploy: repo.value.allow_deploy,
|
||||
cancel_previous_pipeline_events: repo.value.cancel_previous_pipeline_events || [],
|
||||
|
@ -268,4 +290,20 @@ function removeImage(image: string) {
|
|||
|
||||
repoSettings.value.netrc_trusted = repoSettings.value.netrc_trusted.filter((i) => i !== image);
|
||||
}
|
||||
|
||||
const newUser = ref('');
|
||||
function addNewUser() {
|
||||
if (!newUser.value) {
|
||||
return;
|
||||
}
|
||||
repoSettings.value?.approval_allowed_users.push(newUser.value);
|
||||
newUser.value = '';
|
||||
}
|
||||
function removeUser(user: string) {
|
||||
if (!repoSettings.value) {
|
||||
throw new Error('Unexpected: repoSettings should be set');
|
||||
}
|
||||
|
||||
repoSettings.value.approval_allowed_users = repoSettings.value.approval_allowed_users.filter((i) => i !== user);
|
||||
}
|
||||
</script>
|
||||
|
|
Loading…
Reference in a new issue