| 1 | package svcrole |
| 2 | |
| 3 | import ( |
| 4 | "reflect" |
| 5 | "strings" |
| 6 | "testing" |
| 7 | ) |
| 8 | |
| 9 | func TestParse(t *testing.T) { |
| 10 | for _, want := range All() { |
| 11 | got, ok := Parse(string(want)) |
| 12 | if !ok || got != want { |
| 13 | t.Errorf("Parse(%q) = %q,%v; want %q,true", want, got, ok, want) |
| 14 | } |
| 15 | } |
| 16 | // A role name that is not in the catalogue must not parse. "" is the |
| 17 | // value a person's row carries, and it must never resolve to a role. |
| 18 | for _, bad := range []string{"", "owner", "member", "Publish", "admin ", "root"} { |
| 19 | if got, ok := Parse(bad); ok { |
| 20 | t.Errorf("Parse(%q) = %q,true; want false", bad, got) |
| 21 | } |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | func TestAdminPermitsEveryNonEmptySet(t *testing.T) { |
| 26 | // Admin is defined as the union, so it reaches every door that names any |
| 27 | // role at all — including doors that name only Admin. |
| 28 | for _, s := range []Set{ |
| 29 | Allow(Publish), Allow(Instance), Allow(Operate), Allow(Admin), |
| 30 | Allow(Publish, Operate), |
| 31 | } { |
| 32 | if !s.Permits(Admin) { |
| 33 | t.Errorf("Allow(%v).Permits(Admin) = false; want true", s.Roles()) |
| 34 | } |
| 35 | } |
| 36 | // Admin is the union of every role EXCEPT Run, so an Allow set never |
| 37 | // carries Run on Admin's account either. |
| 38 | if Allow(Publish).Permits(Run) { |
| 39 | t.Error("Allow(Publish).Permits(Run) = true; want false") |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | func TestExactlySetsAdmitOnlyTheirRoles(t *testing.T) { |
| 44 | // An Exactly set admits only its listed roles, Admin included. |
| 45 | for _, s := range []Set{Exactly(Run), Exactly(Run, Publish), Exactly(Publish), Exactly(Admin)} { |
| 46 | for _, r := range All() { |
| 47 | listed := false |
| 48 | for _, l := range s.Roles() { |
| 49 | listed = listed || l == r |
| 50 | } |
| 51 | if s.Permits(r) != listed { |
| 52 | t.Errorf("Exactly set %v: Permits(%q) = %v; want %v", s.Roles(), r, s.Permits(r), listed) |
| 53 | } |
| 54 | } |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | func TestExactlyWithNoRolesIsEmptyAndClosed(t *testing.T) { |
| 59 | s := Exactly() |
| 60 | if !s.Empty() { |
| 61 | t.Error("Exactly().Empty() = false; want true") |
| 62 | } |
| 63 | for _, r := range append(All(), Role("")) { |
| 64 | if s.Permits(r) { |
| 65 | t.Errorf("Exactly().Permits(%q) = true; want false", r) |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | func TestAllowRefusesRun(t *testing.T) { |
| 71 | // Every Allow set carries Admin, so Allow(Run) would hand Admin a run |
| 72 | // door. Both the bare and the mixed form must panic, and name Exactly. |
| 73 | for _, roles := range [][]Role{{Run}, {Publish, Run}, {Run, Operate}} { |
| 74 | func() { |
| 75 | defer func() { |
| 76 | got := recover() |
| 77 | if got == nil { |
| 78 | t.Errorf("Allow(%v) did not panic", roles) |
| 79 | return |
| 80 | } |
| 81 | if msg, _ := got.(string); !strings.Contains(msg, "Exactly(Run)") { |
| 82 | t.Errorf("Allow(%v) panic = %v; want it to say to use Exactly(Run)", roles, got) |
| 83 | } |
| 84 | }() |
| 85 | Allow(roles...) |
| 86 | }() |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | func TestRunIsInTheCatalogue(t *testing.T) { |
| 91 | got, ok := Parse("run") |
| 92 | if !ok || got != Run { |
| 93 | t.Fatalf(`Parse("run") = %q, %v; want %q, true`, got, ok, Run) |
| 94 | } |
| 95 | found := false |
| 96 | for _, r := range All() { |
| 97 | found = found || r == Run |
| 98 | } |
| 99 | if !found { |
| 100 | t.Errorf("All() = %v; want it to contain run", All()) |
| 101 | } |
| 102 | } |
| 103 | |
| 104 | func TestExactlyDoesNotAddAdmin(t *testing.T) { |
| 105 | s := Exactly(Run) |
| 106 | if !s.Permits(Run) { |
| 107 | t.Error("Exactly(Run).Permits(Run) = false; want true") |
| 108 | } |
| 109 | for _, r := range All() { |
| 110 | if r != Run && s.Permits(r) { |
| 111 | t.Errorf("Exactly(Run).Permits(%q) = true; want false", r) |
| 112 | } |
| 113 | } |
| 114 | if s.Permits(Admin) { |
| 115 | t.Error("Exactly(Run).Permits(Admin) = true; want false") |
| 116 | } |
| 117 | if got := s.Roles(); !reflect.DeepEqual(got, []Role{Run}) { |
| 118 | t.Errorf("Exactly(Run).Roles() = %v; want [run]", got) |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | func TestExactlyPanicsOnAnUnknownRole(t *testing.T) { |
| 123 | for _, bad := range []Role{"", "rnu", "Run"} { |
| 124 | func() { |
| 125 | defer func() { |
| 126 | if recover() == nil { |
| 127 | t.Errorf("Exactly(%q) did not panic", bad) |
| 128 | } |
| 129 | }() |
| 130 | Exactly(bad) |
| 131 | }() |
| 132 | } |
| 133 | defer func() { |
| 134 | if recover() == nil { |
| 135 | t.Error("Exactly(Run, invalid) did not panic") |
| 136 | } |
| 137 | }() |
| 138 | Exactly(Run, Role("rnu")) |
| 139 | } |
| 140 | |
| 141 | func TestAllowIsExactForNonAdmin(t *testing.T) { |
| 142 | s := Allow(Publish) |
| 143 | if !s.Permits(Publish) { |
| 144 | t.Error("Allow(Publish).Permits(Publish) = false; want true") |
| 145 | } |
| 146 | for _, r := range []Role{Instance, Operate} { |
| 147 | if s.Permits(r) { |
| 148 | t.Errorf("Allow(Publish).Permits(%q) = true; want false", r) |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | func TestEmptySetPermitsNothingIncludingAdmin(t *testing.T) { |
| 154 | // The zero Set is what a door gets if someone constructs one by mistake. |
| 155 | // It must be closed, not open, and Admin must not be an escape hatch |
| 156 | // through it — otherwise "forgot to name a role" reads as "admin-only" |
| 157 | // rather than as the refusal it is. |
| 158 | var s Set |
| 159 | if !s.Empty() { |
| 160 | t.Fatal("zero Set.Empty() = false; want true") |
| 161 | } |
| 162 | for _, r := range append(All(), Role("")) { |
| 163 | if s.Permits(r) { |
| 164 | t.Errorf("zero Set.Permits(%q) = true; want false", r) |
| 165 | } |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | func TestUnknownRoleNeverPermitted(t *testing.T) { |
| 170 | s := Allow(Publish, Instance, Operate, Admin) |
| 171 | for _, r := range []Role{"", "owner", "wildcard", "*"} { |
| 172 | if s.Permits(r) { |
| 173 | t.Errorf("Permits(%q) = true; want false", r) |
| 174 | } |
| 175 | } |
| 176 | } |
| 177 | |
| 178 | func TestRolesIsSortedAndDeduplicated(t *testing.T) { |
| 179 | // Admin is always present: Allow adds it from the union definition, so a |
| 180 | // door naming Publish twice is the set {admin, publish}, sorted. |
| 181 | got := Allow(Operate, Publish, Publish).Roles() |
| 182 | want := []Role{Admin, Operate, Publish} |
| 183 | if !reflect.DeepEqual(got, want) { |
| 184 | t.Errorf("Roles() = %v; want %v", got, want) |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | func TestAllowPanicsOnAnUnknownRole(t *testing.T) { |
| 189 | // A dropped role would be a door silently narrowed — see Allow's comment. |
| 190 | // Registration runs once at server start, so this is the cheapest place |
| 191 | // for a typo to surface. |
| 192 | for _, bad := range []Role{"", "oprate", "Publish", "root"} { |
| 193 | func() { |
| 194 | defer func() { |
| 195 | if recover() == nil { |
| 196 | t.Errorf("Allow(%q) did not panic", bad) |
| 197 | } |
| 198 | }() |
| 199 | Allow(bad) |
| 200 | }() |
| 201 | } |
| 202 | // A typo MIXED WITH valid roles is the dangerous case: it would otherwise |
| 203 | // produce a plausible Set with the mistyped door quietly missing. |
| 204 | defer func() { |
| 205 | if recover() == nil { |
| 206 | t.Error("Allow(Publish, invalid) did not panic") |
| 207 | } |
| 208 | }() |
| 209 | Allow(Publish, Role("oprate")) |
| 210 | } |
| 211 | |
| 212 | func TestEmptyDistinguishesTheTwoForms(t *testing.T) { |
| 213 | if s := Allow(); !s.Empty() { |
| 214 | t.Error("Allow() should be the empty Set — the deliberate closed-door form") |
| 215 | } |
| 216 | if s := Allow(Publish); s.Empty() { |
| 217 | t.Error("Allow(Publish).Empty() = true; Empty must not be unconditionally true") |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | func TestDeployIsMintableAndNarrow(t *testing.T) { |
| 222 | got, ok := Parse("deploy") |
| 223 | if !ok || got != Deploy { |
| 224 | t.Fatalf("Parse(\"deploy\") = %q, %v", got, ok) |
| 225 | } |
| 226 | all := All() |
| 227 | if len(all) < 2 || all[0] != Publish || all[1] != Deploy { |
| 228 | t.Fatalf("All() = %v; want deploy right after publish", all) |
| 229 | } |
| 230 | // Existing doors do not admit it: adding a role must not widen anything. |
| 231 | for _, door := range []Set{Allow(Publish), Allow(Instance), Allow(Operate), Allow(Service)} { |
| 232 | if door.Permits(Deploy) { |
| 233 | t.Fatalf("%v admits deploy", door.Roles()) |
| 234 | } |
| 235 | } |
| 236 | if !Allow(Deploy).Permits(Admin) { |
| 237 | t.Fatal("Allow adds Admin to every set; that changed") |
| 238 | } |
| 239 | } |
| 240 | |