Skip to content

Commit abff0ad

Browse files
authored
DEVPROD-5753: Implement and apply requireHostAccess directive (#8784)
1 parent af914be commit abff0ad

File tree

19 files changed

+617
-85
lines changed

19 files changed

+617
-85
lines changed

graphql/directive_test.go

+128-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/evergreen-ci/evergreen"
99
"github.com/evergreen-ci/evergreen/db"
1010
"github.com/evergreen-ci/evergreen/model"
11+
"github.com/evergreen-ci/evergreen/model/distro"
12+
"github.com/evergreen-ci/evergreen/model/host"
1113
"github.com/evergreen-ci/evergreen/model/user"
1214
restModel "github.com/evergreen-ci/evergreen/rest/model"
1315
"github.com/evergreen-ci/evergreen/testutil"
@@ -160,6 +162,20 @@ func setupPermissions(t *testing.T) {
160162
}
161163
require.NoError(t, roleManager.UpdateRole(distroViewRole))
162164

165+
hostEditRole := gimlet.Role{
166+
ID: "edit_host-id",
167+
Scope: distroScope.ID,
168+
Permissions: map[string]int{evergreen.PermissionHosts: evergreen.HostsEdit.Value},
169+
}
170+
require.NoError(t, roleManager.UpdateRole(hostEditRole))
171+
172+
hostViewRole := gimlet.Role{
173+
ID: "view_host-id",
174+
Scope: distroScope.ID,
175+
Permissions: map[string]int{evergreen.PermissionHosts: evergreen.HostsView.Value},
176+
}
177+
require.NoError(t, roleManager.UpdateRole(hostViewRole))
178+
163179
taskAdminRole := gimlet.Role{
164180
ID: "admin_task",
165181
Scope: projectScope.ID,
@@ -216,7 +232,118 @@ func setupPermissions(t *testing.T) {
216232
}
217233
require.NoError(t, roleManager.UpdateRole(logViewRole))
218234
}
219-
235+
func TestRequireHostAccess(t *testing.T) {
236+
defer func() {
237+
require.NoError(t, db.ClearCollections(host.Collection, user.Collection),
238+
"unable to clear user or host collection")
239+
}()
240+
for tName, tCase := range map[string]func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser){
241+
"FailsWhenHostIdIsNotSpecified": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
242+
obj := any(nil)
243+
_, err := config.Directives.RequireHostAccess(ctx, obj, next, HostAccessLevelEdit)
244+
assert.EqualError(t, err, "input: host not specified")
245+
},
246+
"FailsWhenHostDoesNotExist": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
247+
obj := any(map[string]any{"hostId": "a-non-existent-host-id"})
248+
_, err := config.Directives.RequireHostAccess(ctx, obj, next, HostAccessLevelEdit)
249+
assert.EqualError(t, err, "input: No matching hosts found")
250+
},
251+
"ViewFailsWhenUserDoesNotHaveViewPermission": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
252+
obj := any(map[string]any{"hostId": "host1"})
253+
_, err := config.Directives.RequireHostAccess(ctx, obj, next, HostAccessLevelView)
254+
assert.EqualError(t, err, "input: user 'test_user' does not have permission to access host 'host1'")
255+
},
256+
"EditFailsWhenUserDoesNotHaveEditPermission": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
257+
obj := any(map[string]any{"hostId": "host1"})
258+
_, err := config.Directives.RequireHostAccess(ctx, obj, next, HostAccessLevelEdit)
259+
assert.EqualError(t, err, "input: user 'test_user' does not have permission to access host 'host1'")
260+
},
261+
"ViewSucceedsWhenUserHasViewPermission": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
262+
assert.NoError(t, usr.AddRole(ctx, "view_host-id"))
263+
nextCalled := false
264+
wrappedNext := func(rctx context.Context) (any, error) {
265+
nextCalled = true
266+
return nil, nil
267+
}
268+
obj := any(map[string]any{"hostId": "host1"})
269+
res, err := config.Directives.RequireHostAccess(ctx, obj, wrappedNext, HostAccessLevelView)
270+
assert.NoError(t, err)
271+
assert.Nil(t, res)
272+
assert.Equal(t, true, nextCalled)
273+
assert.NoError(t, usr.RemoveRole(ctx, "view_host-id"))
274+
},
275+
"EditSucceedsWhenUserHasEditPermission": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
276+
assert.NoError(t, usr.AddRole(ctx, "edit_host-id"))
277+
nextCalled := false
278+
wrappedNext := func(rctx context.Context) (any, error) {
279+
nextCalled = true
280+
return nil, nil
281+
}
282+
obj := any(map[string]any{"hostId": "host1"})
283+
res, err := config.Directives.RequireHostAccess(ctx, obj, wrappedNext, HostAccessLevelEdit)
284+
assert.NoError(t, err)
285+
assert.Nil(t, res)
286+
assert.Equal(t, true, nextCalled)
287+
assert.NoError(t, usr.RemoveRole(ctx, "edit_host-id"))
288+
},
289+
"ViewSucceedsWhenHostIsStartedByUser": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
290+
nextCalled := false
291+
wrappedNext := func(rctx context.Context) (any, error) {
292+
nextCalled = true
293+
return nil, nil
294+
}
295+
obj := any(map[string]any{"hostId": "host2"})
296+
res, err := config.Directives.RequireHostAccess(ctx, obj, wrappedNext, HostAccessLevelView)
297+
assert.NoError(t, err)
298+
assert.Nil(t, res)
299+
assert.Equal(t, true, nextCalled)
300+
},
301+
"EditSucceedsWhenHostIsStartedByUser": func(ctx context.Context, t *testing.T, next func(rctx context.Context) (any, error), config Config, usr *user.DBUser) {
302+
nextCalled := false
303+
wrappedNext := func(rctx context.Context) (any, error) {
304+
nextCalled = true
305+
return nil, nil
306+
}
307+
obj := any(map[string]any{"hostId": "host2"})
308+
res, err := config.Directives.RequireHostAccess(ctx, obj, wrappedNext, HostAccessLevelEdit)
309+
assert.NoError(t, err)
310+
assert.Nil(t, res)
311+
assert.Equal(t, true, nextCalled)
312+
},
313+
} {
314+
t.Run(tName, func(t *testing.T) {
315+
ctx, cancel := context.WithCancel(context.Background())
316+
defer cancel()
317+
setupPermissions(t)
318+
usr, err := setupUser(t)
319+
assert.NoError(t, err)
320+
assert.NotNil(t, usr)
321+
ctx = gimlet.AttachUser(ctx, usr)
322+
assert.NotNil(t, ctx)
323+
h1 := host.Host{
324+
Id: "host1",
325+
Distro: distro.Distro{
326+
Id: "distro-id",
327+
},
328+
}
329+
assert.NoError(t, h1.Insert(ctx))
330+
h2 := host.Host{
331+
Id: "host2",
332+
StartedBy: testUser,
333+
Distro: distro.Distro{
334+
Id: "distro-id",
335+
},
336+
}
337+
assert.NoError(t, h2.Insert(ctx))
338+
config := New("/graphql")
339+
assert.NotNil(t, config)
340+
next := func(rctx context.Context) (any, error) {
341+
return nil, nil
342+
}
343+
tCase(ctx, t, next, config, usr)
344+
})
345+
}
346+
}
220347
func TestRequireDistroAccess(t *testing.T) {
221348
setupPermissions(t)
222349
require.NoError(t, db.ClearCollections(model.ProjectRefCollection, user.Collection),

0 commit comments

Comments
 (0)