internal/policy/access_test.go
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}