Skip to content

Commit b0d47d5

Browse files
authored
Merge branch 'main' into relative-taskref-resolution
2 parents 650df35 + 7340740 commit b0d47d5

File tree

7 files changed

+299
-3
lines changed

7 files changed

+299
-3
lines changed

pkg/pipelineascode/match.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ is that what you want? make sure you use -n when generating the secret, eg: echo
137137
// Get the SHA commit info, we want to get the URL and commit title
138138
err = p.vcx.GetCommitInfo(ctx, p.event)
139139
if err != nil {
140-
return repo, err
140+
return repo, fmt.Errorf("could not find commit info: %w", err)
141141
}
142142

143143
// Verify whether the sender of the GitOps command (e.g., /test) has the appropriate permissions to

pkg/pipelineascode/match_test.go

Lines changed: 267 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
package pipelineascode
22

33
import (
4+
"crypto/hmac"
5+
"crypto/sha256"
6+
"encoding/hex"
7+
"fmt"
8+
"net/http"
49
"strings"
510
"testing"
611

@@ -9,10 +14,12 @@ import (
914
"github.com/openshift-pipelines/pipelines-as-code/pkg/apis/pipelinesascode/v1alpha1"
1015
"github.com/openshift-pipelines/pipelines-as-code/pkg/consoleui"
1116
"github.com/openshift-pipelines/pipelines-as-code/pkg/events"
17+
"github.com/openshift-pipelines/pipelines-as-code/pkg/opscomments"
1218
"github.com/openshift-pipelines/pipelines-as-code/pkg/params"
1319
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/clients"
1420
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/info"
1521
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/settings"
22+
"github.com/openshift-pipelines/pipelines-as-code/pkg/params/triggertype"
1623
ghprovider "github.com/openshift-pipelines/pipelines-as-code/pkg/provider/github"
1724
testclient "github.com/openshift-pipelines/pipelines-as-code/pkg/test/clients"
1825
ghtesthelper "github.com/openshift-pipelines/pipelines-as-code/pkg/test/github"
@@ -281,3 +288,263 @@ func TestGetPipelineRunsFromRepo(t *testing.T) {
281288
})
282289
}
283290
}
291+
292+
func TestVerifyRepoAndUser(t *testing.T) {
293+
observerCore, _ := zapobserver.New(zap.InfoLevel)
294+
logger := zap.New(observerCore).Sugar()
295+
296+
payload := []byte(`{"key": "value"}`)
297+
mac := hmac.New(sha256.New, []byte("secret"))
298+
mac.Write(payload)
299+
sha256secret := hex.EncodeToString(mac.Sum(nil))
300+
301+
header := make(http.Header)
302+
header.Set(github.SHA256SignatureHeader, fmt.Sprintf("sha256=%s", sha256secret))
303+
304+
request := &info.Request{
305+
Header: header,
306+
Payload: payload,
307+
}
308+
309+
tests := []struct {
310+
name string
311+
runevent info.Event
312+
repositories []*v1alpha1.Repository
313+
webhookSecret string
314+
wantRepoNil bool
315+
wantErr bool
316+
wantErrMsg string
317+
}{
318+
{
319+
name: "no repository match",
320+
runevent: info.Event{
321+
Organization: "owner",
322+
Repository: "repo",
323+
URL: "https://example.com/owner/repo",
324+
SHA: "123abc",
325+
EventType: triggertype.PullRequest.String(),
326+
TriggerTarget: triggertype.PullRequest,
327+
},
328+
wantRepoNil: true,
329+
wantErr: false,
330+
},
331+
{
332+
name: "missing git_provider section",
333+
runevent: info.Event{
334+
Organization: "owner",
335+
Repository: "repo",
336+
URL: "https://example.com/owner/repo",
337+
SHA: "123abc",
338+
EventType: triggertype.PullRequest.String(),
339+
TriggerTarget: triggertype.PullRequest,
340+
},
341+
repositories: []*v1alpha1.Repository{{
342+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
343+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
344+
}},
345+
wantRepoNil: false,
346+
wantErr: true,
347+
wantErrMsg: "cannot get secret from repository: failed to find git_provider details in repository spec: ns/repo",
348+
},
349+
{
350+
name: "webhook validation failure",
351+
runevent: info.Event{
352+
Organization: "owner",
353+
Repository: "repo",
354+
URL: "https://example.com/owner/repo",
355+
SHA: "123abc",
356+
EventType: triggertype.PullRequest.String(),
357+
TriggerTarget: triggertype.PullRequest,
358+
InstallationID: 1,
359+
Request: &info.Request{Header: http.Header{}},
360+
},
361+
repositories: []*v1alpha1.Repository{{
362+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
363+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
364+
}},
365+
wantRepoNil: false,
366+
wantErr: true,
367+
wantErrMsg: "could not validate payload, check your webhook secret?: no signature has been detected, for security reason we are not allowing webhooks that has no secret",
368+
},
369+
{
370+
name: "webhook secret is not set",
371+
runevent: info.Event{
372+
Organization: "owner",
373+
Repository: "repo",
374+
URL: "https://example.com/owner/repo",
375+
SHA: "123abc",
376+
EventType: triggertype.PullRequest.String(),
377+
TriggerTarget: triggertype.PullRequest,
378+
InstallationID: 1,
379+
Request: request,
380+
},
381+
repositories: []*v1alpha1.Repository{{
382+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
383+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
384+
}},
385+
wantRepoNil: false,
386+
wantErr: true,
387+
wantErrMsg: "could not validate payload, check your webhook secret?: no webhook secret has been set, in repository CR or secret",
388+
},
389+
{
390+
name: "permission denied push comment",
391+
runevent: info.Event{
392+
Organization: "owner",
393+
Repository: "repo",
394+
URL: "https://example.com/owner/repo",
395+
SHA: "123abc",
396+
EventType: opscomments.TestAllCommentEventType.String(),
397+
TriggerTarget: triggertype.Push,
398+
Sender: "intruder",
399+
InstallationID: 1,
400+
Request: request,
401+
},
402+
repositories: []*v1alpha1.Repository{{
403+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
404+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
405+
}},
406+
webhookSecret: "secret",
407+
wantRepoNil: true,
408+
wantErr: true,
409+
wantErrMsg: "failed to run create status, user is not allowed to run the CI",
410+
},
411+
{
412+
name: "permission denied pull_request comment pending approval",
413+
runevent: info.Event{
414+
Organization: "owner",
415+
Repository: "repo",
416+
URL: "https://example.com/owner/repo",
417+
SHA: "123abc",
418+
EventType: triggertype.PullRequest.String(),
419+
TriggerTarget: triggertype.PullRequest,
420+
Sender: "outsider",
421+
InstallationID: 1,
422+
Request: request,
423+
},
424+
repositories: []*v1alpha1.Repository{{
425+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
426+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
427+
}},
428+
webhookSecret: "secret",
429+
wantRepoNil: true,
430+
wantErr: true,
431+
wantErrMsg: "failed to run create status, user is not allowed to run the CI",
432+
},
433+
{
434+
name: "commit not found",
435+
runevent: info.Event{
436+
Organization: "owner",
437+
Repository: "repo",
438+
URL: "https://example.com/owner/repo",
439+
SHA: "",
440+
EventType: triggertype.PullRequest.String(),
441+
TriggerTarget: triggertype.PullRequest,
442+
InstallationID: 1,
443+
Sender: "owner",
444+
Request: request,
445+
},
446+
repositories: []*v1alpha1.Repository{{
447+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
448+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
449+
}},
450+
webhookSecret: "secret",
451+
wantRepoNil: false,
452+
wantErr: true,
453+
wantErrMsg: "could not find commit info",
454+
},
455+
{
456+
name: "happy path",
457+
runevent: info.Event{
458+
Organization: "owner",
459+
Repository: "repo",
460+
URL: "https://example.com/owner/repo",
461+
SHA: "123abc",
462+
EventType: triggertype.PullRequest.String(),
463+
TriggerTarget: triggertype.PullRequest,
464+
InstallationID: 1,
465+
Sender: "owner",
466+
Request: request,
467+
},
468+
repositories: []*v1alpha1.Repository{{
469+
ObjectMeta: metav1.ObjectMeta{Name: "repo", Namespace: "ns"},
470+
Spec: v1alpha1.RepositorySpec{URL: "https://example.com/owner/repo"},
471+
}},
472+
webhookSecret: "secret",
473+
wantRepoNil: false,
474+
wantErr: false,
475+
},
476+
}
477+
478+
pacInfo := &info.PacOpts{Settings: settings.DefaultSettings()}
479+
480+
for _, tt := range tests {
481+
t.Run(tt.name, func(t *testing.T) {
482+
baseCtx, _ := rtesting.SetupFakeContext(t)
483+
ctx := info.StoreNS(baseCtx, "pac")
484+
485+
ghClient, mux, _, teardown := ghtesthelper.SetupGH()
486+
defer teardown()
487+
488+
// commit endpoint
489+
commitPath := fmt.Sprintf("/repos/%s/%s/git/commits/%s", tt.runevent.Organization, tt.runevent.Repository, tt.runevent.SHA)
490+
mux.HandleFunc(commitPath, func(rw http.ResponseWriter, _ *http.Request) {
491+
if tt.runevent.SHA == "" {
492+
rw.WriteHeader(http.StatusNotFound)
493+
return
494+
}
495+
fmt.Fprint(rw, `{"sha":"123abc","html_url":"https://example.com/commit/123abc","message":"msg"}`)
496+
})
497+
498+
// org members empty
499+
mux.HandleFunc(fmt.Sprintf("/orgs/%s/members", tt.runevent.Organization), func(rw http.ResponseWriter, _ *http.Request) { fmt.Fprint(rw, `[]`) })
500+
// collaborator check – return 404 for non‐collaborator sender when defined
501+
if tt.runevent.Sender != "" && tt.runevent.Sender != tt.runevent.Organization {
502+
mux.HandleFunc(
503+
fmt.Sprintf("/repos/%s/%s/collaborators/%s", tt.runevent.Organization, tt.runevent.Repository, tt.runevent.Sender),
504+
func(rw http.ResponseWriter, _ *http.Request) { rw.WriteHeader(http.StatusNotFound) },
505+
)
506+
}
507+
// status endpoint stub (used when CreateStatus is called)
508+
mux.HandleFunc(
509+
fmt.Sprintf("/repos/%s/%s/statuses/%s", tt.runevent.Organization, tt.runevent.Repository, tt.runevent.SHA),
510+
func(rw http.ResponseWriter, _ *http.Request) { fmt.Fprint(rw, `{}`) },
511+
)
512+
513+
vcx := &ghprovider.Provider{Token: github.Ptr("token"), Logger: logger}
514+
vcx.SetGithubClient(ghClient)
515+
vcx.SetPacInfo(pacInfo)
516+
517+
k8int := &kitesthelper.KinterfaceTest{GetSecretResult: map[string]string{"pipelines-as-code-secret": tt.webhookSecret}}
518+
519+
stdata, _ := testclient.SeedTestData(t, ctx, testclient.Data{Repositories: tt.repositories /*Secret: []*corev1.Secret{secret}*/})
520+
in := info.NewInfo()
521+
cs := &params.Run{
522+
Info: in,
523+
Clients: clients.Clients{
524+
PipelineAsCode: stdata.PipelineAsCode,
525+
Kube: stdata.Kube,
526+
Tekton: stdata.Pipeline,
527+
Log: logger,
528+
},
529+
}
530+
cs.Clients.SetConsoleUI(consoleui.FallBackConsole{})
531+
532+
ev := tt.runevent
533+
ev.Provider = &info.Provider{Token: "token", WebhookSecret: tt.webhookSecret}
534+
535+
p := NewPacs(&ev, vcx, cs, pacInfo, k8int, logger, nil)
536+
repo, err := p.verifyRepoAndUser(ctx)
537+
assert.Assert(t, (err != nil) == tt.wantErr)
538+
539+
if tt.wantErr {
540+
assert.ErrorContains(t, err, tt.wantErrMsg)
541+
}
542+
543+
if tt.wantRepoNil {
544+
assert.Assert(t, repo == nil)
545+
} else {
546+
assert.Assert(t, repo != nil)
547+
}
548+
})
549+
}
550+
}

pkg/provider/bitbucketcloud/types/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//revive:disable-next-line:var-naming
12
package types
23

34
type Workspace struct {

pkg/provider/bitbucketdatacenter/parse_payload.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ func (v *Provider) ParsePayload(_ context.Context, run *params.Run, request *htt
164164
return nil, fmt.Errorf("push event contains no commits under 'changes'; cannot proceed")
165165
}
166166

167+
// Check for branch deletion - if any change is a DELETE type with zero hash, skip processing
168+
for _, change := range e.Changes {
169+
if provider.IsZeroSHA(change.ToHash) && change.Type == "DELETE" {
170+
return nil, fmt.Errorf("branch delete event is not supported; cannot proceed")
171+
}
172+
}
173+
167174
if len(e.Commits) == 0 {
168175
return nil, fmt.Errorf("push event contains no commits; cannot proceed")
169176
}

pkg/provider/bitbucketdatacenter/parse_payload_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,20 @@ func TestParsePayload(t *testing.T) {
709709
expEvent: ev1,
710710
wantSHA: "abcd",
711711
},
712+
{
713+
name: "branch/deleted with zero hash",
714+
eventType: "repo:refs_changed",
715+
payloadEvent: bbv1test.MakePushEvent(ev1, []types.PushRequestEventChange{
716+
{
717+
ToHash: "0000000000000000000000000000000000000000",
718+
RefID: "refs/heads/feature-branch",
719+
Type: "DELETE",
720+
},
721+
}, []types.Commit{},
722+
),
723+
expEvent: ev1,
724+
wantErrSubstr: "branch delete event is not supported; cannot proceed",
725+
},
712726
}
713727
for _, tt := range tests {
714728
t.Run(tt.name, func(t *testing.T) {
@@ -735,10 +749,15 @@ func TestParsePayload(t *testing.T) {
735749
return
736750
}
737751
assert.NilError(t, err)
738-
752+
// Handle case where expEvent is nil (e.g., branch deletion)
753+
if tt.expEvent == nil {
754+
if got != nil {
755+
t.Fatalf("expected event to be nil, got: %+v", got)
756+
}
757+
return
758+
}
739759
// assert SHA ID
740760
assert.Equal(t, tt.wantSHA, got.SHA)
741-
742761
assert.Equal(t, got.AccountID, tt.expEvent.AccountID)
743762

744763
// test that we got slashed

pkg/provider/bitbucketdatacenter/types/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//revive:disable-next-line:var-naming
12
package types
23

34
type UserWithMetadata struct {

pkg/secrets/types/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
//revive:disable-next-line:var-naming
12
package types
23

34
type SecretValue struct {

0 commit comments

Comments
 (0)