Skip to content

Commit ab2a33d

Browse files
committed
fix(gitea): check write/admin permission instead of collaborator only
Replace IsCollaborator (which returns true for read-only collaborators) with CollaboratorPermission to verify the sender has write or admin or owner access before allowing pipeline runs. Also fix CreateForkPullRequest to grant access to SecondUserName instead of TargetRefName. Signed-off-by: Zaki Shaikh <zashaikh@redhat.com>
1 parent 8482946 commit ab2a33d

5 files changed

Lines changed: 97 additions & 17 deletions

File tree

pkg/provider/gitea/acl.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,17 @@ func (v *Provider) listOrgTeams(org, sender string) ([]*forgejo.Team, error) {
226226
}
227227

228228
func (v *Provider) checkSenderRepoMembership(_ context.Context, runevent *info.Event) (bool, error) {
229-
ret, _, err := v.Client().IsCollaborator(runevent.Organization, runevent.Repository, runevent.Sender)
230-
return ret, err
229+
permissionResult, _, err := v.Client().CollaboratorPermission(runevent.Organization, runevent.Repository, runevent.Sender)
230+
if err != nil {
231+
return false, err
232+
}
233+
if permissionResult != nil &&
234+
(permissionResult.Permission == forgejo.AccessModeOwner ||
235+
permissionResult.Permission == forgejo.AccessModeAdmin ||
236+
permissionResult.Permission == forgejo.AccessModeWrite) {
237+
return true, nil
238+
}
239+
return false, nil
231240
}
232241

233242
// getFileFromDefaultBranch will get a file directly from the Default BaseBranch as

pkg/provider/gitea/acl_test.go

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func TestOkToTestComment(t *testing.T) {
228228
runevent: info.Event{
229229
Organization: "owner",
230230
Repository: "repo",
231-
Sender: "nonowner",
231+
Sender: "notowner",
232232
EventType: "issue_comment",
233233
Event: issueCommentPayload,
234234
},
@@ -298,7 +298,7 @@ func TestOkToTestComment(t *testing.T) {
298298
runevent: info.Event{
299299
Organization: "owner",
300300
Repository: "repo",
301-
Sender: "nonowner",
301+
Sender: "notowner",
302302
EventType: "issue_comment",
303303
Event: issueCommentPayload,
304304
},
@@ -326,8 +326,9 @@ func TestOkToTestComment(t *testing.T) {
326326
func(rw http.ResponseWriter, _ *http.Request) {
327327
fmt.Fprint(rw, tt.commentsReply)
328328
})
329-
mux.HandleFunc("/repos/owner/collaborators", func(rw http.ResponseWriter, _ *http.Request) {
330-
fmt.Fprint(rw, "[]")
329+
mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", tt.runevent.Organization,
330+
tt.runevent.Repository, tt.runevent.Sender), func(rw http.ResponseWriter, _ *http.Request) {
331+
fmt.Fprint(rw, `{"permission": "none"}`)
331332
})
332333
ctx, _ := rtesting.SetupFakeContext(t)
333334
gprovider := Provider{
@@ -354,7 +355,9 @@ func TestOkToTestComment(t *testing.T) {
354355
func TestAclCheckAll(t *testing.T) {
355356
type allowedRules struct {
356357
ownerFile bool
358+
read bool
357359
collabo bool
360+
admin bool
358361
}
359362
tests := []struct {
360363
name string
@@ -364,7 +367,7 @@ func TestAclCheckAll(t *testing.T) {
364367
allowed bool
365368
}{
366369
{
367-
name: "allowed_from_org/sender allowed_from_org in collabo",
370+
name: "allowed when sender has repository write collaborator permission",
368371
runevent: info.Event{
369372
Organization: "collabo",
370373
Repository: "repo",
@@ -375,7 +378,18 @@ func TestAclCheckAll(t *testing.T) {
375378
wantErr: false,
376379
},
377380
{
378-
name: "allowed_from_org/sender allowed_from_org from owner file",
381+
name: "allowed when sender has repository admin permission",
382+
runevent: info.Event{
383+
Organization: "collabo",
384+
Repository: "repo",
385+
Sender: "login_allowed",
386+
},
387+
allowedRules: allowedRules{admin: true},
388+
allowed: true,
389+
wantErr: false,
390+
},
391+
{
392+
name: "allowed when sender is approver in OWNERS file",
379393
runevent: info.Event{
380394
Organization: "collabo",
381395
Repository: "repo",
@@ -388,7 +402,7 @@ func TestAclCheckAll(t *testing.T) {
388402
wantErr: false,
389403
},
390404
{
391-
name: "disallowed/sender not allowed_from_org in collabo",
405+
name: "disallowed when sender has no collaborator or OWNERS approval",
392406
runevent: info.Event{
393407
Organization: "denied",
394408
Repository: "denied",
@@ -397,6 +411,17 @@ func TestAclCheckAll(t *testing.T) {
397411
allowed: false,
398412
wantErr: false,
399413
},
414+
{
415+
name: "allowed when sender has repository read permission",
416+
runevent: info.Event{
417+
Organization: "denied",
418+
Repository: "denied",
419+
Sender: "notallowed",
420+
},
421+
allowedRules: allowedRules{read: true},
422+
allowed: false,
423+
wantErr: false,
424+
},
400425
}
401426
for _, tt := range tests {
402427
t.Run(tt.name, func(t *testing.T) {
@@ -411,12 +436,20 @@ func TestAclCheckAll(t *testing.T) {
411436
Logger: logger,
412437
}
413438

414-
if tt.allowedRules.collabo {
415-
mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/collaborators/%s", tt.runevent.Organization,
416-
tt.runevent.Repository, tt.runevent.Sender), func(rw http.ResponseWriter, _ *http.Request) {
417-
rw.WriteHeader(http.StatusNoContent)
418-
})
419-
}
439+
mux.HandleFunc(fmt.Sprintf("/repos/%s/%s/collaborators/%s/permission", tt.runevent.Organization,
440+
tt.runevent.Repository, tt.runevent.Sender), func(rw http.ResponseWriter, _ *http.Request) {
441+
rw.WriteHeader(http.StatusOK)
442+
permission := "none"
443+
switch {
444+
case tt.allowedRules.admin:
445+
permission = "admin"
446+
case tt.allowedRules.collabo:
447+
permission = "write"
448+
case tt.allowedRules.read:
449+
permission = "read"
450+
}
451+
fmt.Fprintf(rw, `{"permission": "%s"}`, permission)
452+
})
420453
if tt.allowedRules.ownerFile {
421454
url := fmt.Sprintf("/repos/%s/%s/contents/OWNERS", tt.runevent.Organization, tt.runevent.Repository)
422455
mux.HandleFunc(url, func(rw http.ResponseWriter, r *http.Request) {

test/gitea_access_control_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/openshift-pipelines/pipelines-as-code/test/pkg/scm"
2222
twait "github.com/openshift-pipelines/pipelines-as-code/test/pkg/wait"
2323
tektonv1 "github.com/tektoncd/pipeline/pkg/apis/pipeline/v1"
24+
"github.com/tektoncd/pipeline/pkg/names"
2425
"gotest.tools/v3/assert"
2526
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2627
)
@@ -189,12 +190,44 @@ func TestGiteaACLOrgAllowed(t *testing.T) {
189190
secondcnx, _, err := tgitea.CreateGiteaUserSecondCnx(topts, topts.TargetRefName, topts.GiteaPassword)
190191
assert.NilError(t, err)
191192

192-
tgitea.CreateForkPullRequest(t, topts, secondcnx, "read")
193+
tgitea.CreateForkPullRequest(t, topts, secondcnx, "write")
193194
topts.CheckForStatus = "success"
194195
tgitea.WaitForStatus(t, topts, "heads/"+topts.TargetRefName, "", false)
195196
topts.GiteaCNX = adminCnx
196197
}
197198

199+
func TestGiteaACLOrgWriteAndAdminAccess(t *testing.T) {
200+
topts := &tgitea.TestOpts{
201+
TargetEvent: triggertype.PullRequest.String(),
202+
YAMLFiles: map[string]string{
203+
".tekton/pr.yaml": "testdata/pipelinerun.yaml",
204+
},
205+
ExpectEvents: false,
206+
CheckForNumberStatus: 2,
207+
}
208+
_, f := tgitea.TestPR(t, topts)
209+
defer f()
210+
adminCnx := topts.GiteaCNX
211+
212+
topts.SecondUserName = topts.TargetRefName
213+
secondcnx, _, err := tgitea.CreateGiteaUserSecondCnx(topts, topts.SecondUserName, topts.GiteaPassword)
214+
assert.NilError(t, err)
215+
tgitea.CreateForkPullRequest(t, topts, secondcnx, "write")
216+
topts.CheckForStatus = "success"
217+
tgitea.WaitForStatus(t, topts, "heads/"+topts.TargetRefName, "", false)
218+
219+
// third user setup to give it admin access to the repo and check the pipeline run are created.
220+
thirdUserName := names.SimpleNameGenerator.RestrictLengthWithRandomSuffix("pac-e2e-test")
221+
topts.SecondUserName = thirdUserName
222+
thirdcnx, _, err := tgitea.CreateGiteaUserSecondCnx(topts, topts.SecondUserName, topts.GiteaPassword)
223+
assert.NilError(t, err)
224+
tgitea.CreateForkPullRequest(t, topts, thirdcnx, "admin")
225+
topts.CheckForStatus = "success"
226+
topts.CheckForNumberStatus = 3
227+
tgitea.WaitForStatus(t, topts, "heads/"+topts.TargetRefName, "", false)
228+
topts.GiteaCNX = adminCnx
229+
}
230+
198231
// TestGiteaACLOrgPendingApproval tests when non authorized user sends a PR the status of CI shows as pending.
199232
func TestGiteaACLOrgPendingApproval(t *testing.T) {
200233
topts := &tgitea.TestOpts{

test/pkg/gitea/scm.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,11 @@ func CreateForkPullRequest(t *testing.T, topts *TestOpts, secondcnx pgitea.Provi
253253
topts.ParamsRun.Clients.Log.Infof("Forked repository %s has been created", forkrepo.CloneURL)
254254

255255
if accessMode != "" {
256-
assert.NilError(t, CreateAccess(topts, topts.TargetRefName, accessMode))
256+
userName := topts.SecondUserName
257+
if userName == "" {
258+
userName = topts.TargetRefName
259+
}
260+
assert.NilError(t, CreateAccess(topts, userName, accessMode))
257261
}
258262

259263
pr, _, err := secondcnx.Client().CreatePullRequest(topts.Opts.Organization, topts.TargetRefName,

test/pkg/gitea/test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ type TestOpts struct {
6767
FileChanges []scm.FileChange
6868
CreateSecret []corev1.Secret
6969
ProviderType string // defaults to "forgejo" if empty
70+
SecondUserName string
7071
}
7172

7273
func PostCommentOnPullRequest(t *testing.T, topt *TestOpts, body string) {

0 commit comments

Comments
 (0)