internal/policy/access_test.go

v1.27.0
gitbay/internal/policy/access_test.go history · blame · raw

166 lines · 5853 bytes

  1package policy
  2
  3import (
  4	"strings"
  5	"testing"
  6
  7	"gitbay.org/gitbay/internal/store"
  8)
  9
 10var (
 11	owner    = store.User{ID: 1, Username: "alice"}
 12	stranger = store.User{ID: 2, Username: "bob"}
 13	priv     = store.Repo{ID: 10, OwnerKind: "user", OwnerID: 1, OwnerName: "alice", Name: "p", Visibility: "private"}
 14	pub      = store.Repo{ID: 11, OwnerKind: "user", OwnerID: 1, OwnerName: "alice", Name: "q", Visibility: "public"}
 15)
 16
 17func TestAccessMatrix(t *testing.T) {
 18	cases := []struct {
 19		name  string
 20		user  store.User
 21		repo  store.Repo
 22		grant string
 23		read  bool
 24		write bool
 25		admin bool
 26	}{
 27		{"owner private", owner, priv, "", true, true, true},
 28		{"stranger private no grant", stranger, priv, "", false, false, false},
 29		{"stranger private read", stranger, priv, "read", true, false, false},
 30		{"stranger private write", stranger, priv, "write", true, true, false},
 31		{"stranger private admin", stranger, priv, "admin", true, true, true},
 32		{"stranger public no grant", stranger, pub, "", true, false, false},
 33		{"stranger public write", stranger, pub, "write", true, true, false},
 34	}
 35	for _, tc := range cases {
 36		t.Run(tc.name, func(t *testing.T) {
 37			if got := CanRead(tc.user, tc.repo, tc.grant); got != tc.read {
 38				t.Errorf("CanRead = %v, want %v", got, tc.read)
 39			}
 40			if got := CanWrite(tc.user, tc.repo, tc.grant); got != tc.write {
 41				t.Errorf("CanWrite = %v, want %v", got, tc.write)
 42			}
 43			if got := CanAdmin(tc.user, tc.repo, tc.grant); got != tc.admin {
 44				t.Errorf("CanAdmin = %v, want %v", got, tc.admin)
 45			}
 46		})
 47	}
 48}
 49
 50func TestScopeAllowsGit(t *testing.T) {
 51	cases := []struct {
 52		scope string
 53		repo  string
 54		write bool
 55		want  bool
 56	}{
 57		{"full", "a/b", true, true},
 58		{"git", "a/b", true, true},
 59		{"deploy:7:ro", "a/b", false, false}, // deploy keys never pass the account path
 60		{"", "a/b", false, false},
 61	}
 62	for _, tc := range cases {
 63		if got := ScopeAllowsGit(tc.scope, tc.repo, tc.write); got != tc.want {
 64			t.Errorf("ScopeAllowsGit(%q, %q, write=%v) = %v, want %v", tc.scope, tc.repo, tc.write, got, tc.want)
 65		}
 66	}
 67}
 68
 69func TestDeployScopeAllows(t *testing.T) {
 70	cases := []struct {
 71		scope  string
 72		repoID int64
 73		write  bool
 74		want   bool
 75	}{
 76		{"deploy:7:ro", 7, false, true},
 77		{"deploy:7:ro", 7, true, false},
 78		{"deploy:7:rw", 7, true, true},
 79		{"deploy:7:rw", 8, false, false}, // wrong repo
 80		{"deploy:7", 7, false, false},    // malformed
 81		{"full", 7, false, false},        // not a deploy scope
 82	}
 83	for _, tc := range cases {
 84		if got := DeployScopeAllows(tc.scope, tc.repoID, tc.write); got != tc.want {
 85			t.Errorf("DeployScopeAllows(%q, %d, write=%v) = %v, want %v", tc.scope, tc.repoID, tc.write, got, tc.want)
 86		}
 87	}
 88}
 89
 90func TestCheckPush(t *testing.T) {
 91	repo := store.Repo{Settings: store.RepoSettings{ProtectedBranches: []string{"main"}}}
 92	cases := []struct {
 93		name    string
 94		updates []RefUpdate
 95		denied  bool
 96	}{
 97		{"normal push to protected", []RefUpdate{{Ref: "refs/heads/main"}}, false},
 98		{"force to protected", []RefUpdate{{Ref: "refs/heads/main", IsForce: true}}, true},
 99		{"delete protected", []RefUpdate{{Ref: "refs/heads/main", IsDelete: true}}, true},
100		{"force to unprotected", []RefUpdate{{Ref: "refs/heads/dev", IsForce: true}}, false},
101		{"delete unprotected", []RefUpdate{{Ref: "refs/heads/dev", IsDelete: true}}, false},
102		{"mr namespace", []RefUpdate{{Ref: "refs/merge-requests/1/head"}}, true},
103		{"tag alongside protected", []RefUpdate{{Ref: "refs/tags/v1"}, {Ref: "refs/heads/main"}}, false},
104	}
105	for _, tc := range cases {
106		t.Run(tc.name, func(t *testing.T) {
107			msg := CheckPush(repo, tc.updates)
108			if (msg != "") != tc.denied {
109				t.Errorf("CheckPush = %q, denied should be %v", msg, tc.denied)
110			}
111		})
112	}
113}
114
115// A protected tag (glob) can be created once and neither moved nor
116// deleted (#201).
117func TestCheckPushProtectedTags(t *testing.T) {
118	repo := store.Repo{Settings: store.RepoSettings{ProtectedTags: []string{"v*"}}}
119	const zero = "0000000000000000000000000000000000000000"
120	cases := []struct {
121		name    string
122		updates []RefUpdate
123		denied  bool
124	}{
125		{"create protected", []RefUpdate{{Ref: "refs/tags/v1.0", Old: zero, New: "abc"}}, false},
126		{"delete protected", []RefUpdate{{Ref: "refs/tags/v1.0", Old: "abc", New: zero, IsDelete: true}}, true},
127		{"move protected", []RefUpdate{{Ref: "refs/tags/v1.0", Old: "abc", New: "def", IsForce: true}}, true},
128		{"delete unmatched", []RefUpdate{{Ref: "refs/tags/nightly", Old: "abc", New: zero, IsDelete: true}}, false},
129	}
130	for _, tc := range cases {
131		t.Run(tc.name, func(t *testing.T) {
132			msg := CheckPush(repo, tc.updates)
133			if (msg != "") != tc.denied {
134				t.Errorf("CheckPush = %q, denied should be %v", msg, tc.denied)
135			}
136		})
137	}
138}
139
140// With require_mr, a protected branch takes no direct push once it
141// exists; creating it and pushing elsewhere are unaffected (#197).
142func TestCheckPushRequireMR(t *testing.T) {
143	repo := store.Repo{Settings: store.RepoSettings{ProtectedBranches: []string{"main"}, RequireMR: true}}
144	const zero = "0000000000000000000000000000000000000000"
145	cases := []struct {
146		name    string
147		updates []RefUpdate
148		denied  bool
149	}{
150		{"update protected", []RefUpdate{{Ref: "refs/heads/main", Old: "abc", New: "def"}}, true},
151		{"create protected", []RefUpdate{{Ref: "refs/heads/main", Old: zero, New: "def"}}, false},
152		{"delete protected", []RefUpdate{{Ref: "refs/heads/main", Old: "abc", New: zero, IsDelete: true}}, true},
153		{"update unprotected", []RefUpdate{{Ref: "refs/heads/dev", Old: "abc", New: "def"}}, false},
154	}
155	for _, tc := range cases {
156		t.Run(tc.name, func(t *testing.T) {
157			msg := CheckPush(repo, tc.updates)
158			if (msg != "") != tc.denied {
159				t.Errorf("CheckPush = %q, denied should be %v", msg, tc.denied)
160			}
161		})
162	}
163	if msg := CheckPush(repo, cases[0].updates); !strings.Contains(msg, "merge requests only") {
164		t.Errorf("message %q", msg)
165	}
166}