1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
14 "git.arvados.org/arvados.git/sdk/go/arvados"
15 "git.arvados.org/arvados.git/sdk/go/arvadostest"
19 // Gocheck boilerplate
20 func Test(t *testing.T) {
24 type TestSuite struct {
26 users map[string]arvados.User
29 func (s *TestSuite) SetUpTest(c *C) {
30 ac := arvados.NewClientFromEnv()
31 u, err := ac.CurrentUser()
33 // Check that the parent group doesn't exist
34 sysUserUUID := u.UUID[:12] + "000000000000000"
35 gl := arvados.GroupList{}
36 params := arvados.ResourceListParams{
37 Filters: []arvados.Filter{{
44 Operand: "Externally synchronized groups",
47 ac.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
48 c.Assert(gl.ItemsAvailable, Equals, 0)
50 os.Args = []string{"cmd", "somefile.csv"}
51 config, err := GetConfig()
53 config.UserID = "email"
54 // Confirm that the parent group was created
55 gl = arvados.GroupList{}
56 ac.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
57 c.Assert(gl.ItemsAvailable, Equals, 1)
58 // Config set up complete, save config for further testing
61 // Fetch current user list
62 ul := arvados.UserList{}
63 params = arvados.ResourceListParams{
64 Filters: []arvados.Filter{{
67 Operand: s.cfg.SysUserUUID,
70 ac.RequestAndDecode(&ul, "GET", "/arvados/v1/users", nil, params)
71 c.Assert(ul.ItemsAvailable, Not(Equals), 0)
72 s.users = make(map[string]arvados.User)
73 for _, u := range ul.Items {
76 c.Assert(len(s.users), Not(Equals), 0)
79 func (s *TestSuite) TearDownTest(c *C) {
81 // Reset database to fixture state after every test run.
82 err := s.cfg.Client.RequestAndDecode(&dst, "POST", "/database/reset", nil, nil)
86 var _ = Suite(&TestSuite{})
88 // MakeTempCSVFile creates a temp file with data as comma separated values
89 func MakeTempCSVFile(data [][]string) (f *os.File, err error) {
90 f, err = ioutil.TempFile("", "test_sync_remote_groups")
94 for _, line := range data {
95 fmt.Fprintf(f, "%s\n", strings.Join(line, ","))
101 // GroupMembershipExists checks that both needed links exist between user and group
102 func GroupMembershipExists(ac *arvados.Client, userUUID string, groupUUID string, perm string) bool {
104 // Check Group -> User can_read permission
105 params := arvados.ResourceListParams{
106 Filters: []arvados.Filter{{
109 Operand: "permission",
124 ac.RequestAndDecode(&ll, "GET", "/arvados/v1/links", nil, params)
128 // Check User -> Group can_write permission
129 params = arvados.ResourceListParams{
130 Filters: []arvados.Filter{{
133 Operand: "permission",
148 ac.RequestAndDecode(&ll, "GET", "/arvados/v1/links", nil, params)
152 // If named group exists, return its UUID
153 func RemoteGroupExists(cfg *ConfigParams, groupName string) (uuid string, err error) {
154 gl := arvados.GroupList{}
155 params := arvados.ResourceListParams{
156 Filters: []arvados.Filter{{
163 Operand: cfg.SysUserUUID,
170 err = cfg.Client.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
174 if gl.ItemsAvailable == 0 {
175 // No group with this name
177 } else if gl.ItemsAvailable == 1 {
179 uuid = gl.Items[0].UUID
181 // This should never happen
183 err = fmt.Errorf("more than 1 group found with the same name and parent")
188 func (s *TestSuite) TestParseFlagsWithPositionalArgument(c *C) {
189 cfg := ConfigParams{}
190 os.Args = []string{"cmd", "-verbose", "/tmp/somefile.csv"}
191 err := ParseFlags(&cfg)
193 c.Check(cfg.Path, Equals, "/tmp/somefile.csv")
194 c.Check(cfg.Verbose, Equals, true)
197 func (s *TestSuite) TestParseFlagsWithoutPositionalArgument(c *C) {
198 os.Args = []string{"cmd", "-verbose"}
199 err := ParseFlags(&ConfigParams{})
200 c.Assert(err, NotNil)
203 func (s *TestSuite) TestGetUserID(c *C) {
205 Email: "testuser@example.com",
206 Username: "Testuser",
208 email, err := GetUserID(u, "email")
210 c.Check(email, Equals, "testuser@example.com")
211 _, err = GetUserID(u, "bogus")
212 c.Assert(err, NotNil)
215 func (s *TestSuite) TestGetConfig(c *C) {
216 os.Args = []string{"cmd", "/tmp/somefile.csv"}
217 cfg, err := GetConfig()
219 c.Check(cfg.SysUserUUID, NotNil)
220 c.Check(cfg.Client, NotNil)
221 c.Check(cfg.ParentGroupUUID, NotNil)
222 c.Check(cfg.ParentGroupName, Equals, "Externally synchronized groups")
225 // Ignore leading & trailing spaces on group & users names
226 func (s *TestSuite) TestIgnoreSpaces(c *C) {
227 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
228 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
229 // Confirm that the groups don't exist
230 for _, groupName := range []string{"TestGroup1", "TestGroup2", "Test Group 3"} {
231 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
233 c.Assert(groupUUID, Equals, "")
236 {" TestGroup1", activeUserEmail},
237 {"TestGroup2 ", " " + activeUserEmail},
238 {" Test Group 3 ", activeUserEmail + " "},
240 tmpfile, err := MakeTempCSVFile(data)
242 defer os.Remove(tmpfile.Name()) // clean up
243 s.cfg.Path = tmpfile.Name()
246 // Check that 3 groups were created correctly, and have the active user as
248 for _, groupName := range []string{"TestGroup1", "TestGroup2", "Test Group 3"} {
249 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
251 c.Assert(groupUUID, Not(Equals), "")
252 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
256 // Error out when records have <2 or >3 records
257 func (s *TestSuite) TestWrongNumberOfFields(c *C) {
258 for _, testCase := range [][][]string{
260 {{"field1", "field2", "field3", "field4"}},
261 {{"field1", "field2", "field3", "field4", "field5"}},
263 tmpfile, err := MakeTempCSVFile(testCase)
265 defer os.Remove(tmpfile.Name())
266 s.cfg.Path = tmpfile.Name()
268 c.Assert(err, Not(IsNil))
272 // Check different membership permissions
273 func (s *TestSuite) TestMembershipLevels(c *C) {
274 userEmail := s.users[arvadostest.ActiveUserUUID].Email
275 userUUID := s.users[arvadostest.ActiveUserUUID].UUID
277 {"TestGroup1", userEmail, "can_read"},
278 {"TestGroup2", userEmail, "can_write"},
279 {"TestGroup3", userEmail, "can_manage"},
280 {"TestGroup4", userEmail, "invalid_permission"},
282 tmpfile, err := MakeTempCSVFile(data)
284 defer os.Remove(tmpfile.Name()) // clean up
285 s.cfg.Path = tmpfile.Name()
288 for _, record := range data {
289 groupName := record[0]
290 permLevel := record[2]
291 if permLevel != "invalid_permission" {
292 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
294 c.Assert(groupUUID, Not(Equals), "")
295 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, permLevel), Equals, true)
297 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
299 c.Assert(groupUUID, Equals, "")
304 // Check membership level change
305 func (s *TestSuite) TestMembershipLevelUpdate(c *C) {
306 userEmail := s.users[arvadostest.ActiveUserUUID].Email
307 userUUID := s.users[arvadostest.ActiveUserUUID].UUID
308 groupName := "TestGroup1"
309 // Give read permissions
310 tmpfile, err := MakeTempCSVFile([][]string{{groupName, userEmail, "can_read"}})
312 defer os.Remove(tmpfile.Name()) // clean up
313 s.cfg.Path = tmpfile.Name()
317 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
319 c.Assert(groupUUID, Not(Equals), "")
320 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, true)
321 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, false)
322 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, false)
324 // Give write permissions
325 tmpfile, err = MakeTempCSVFile([][]string{{groupName, userEmail, "can_write"}})
327 defer os.Remove(tmpfile.Name()) // clean up
328 s.cfg.Path = tmpfile.Name()
332 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, false)
333 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, true)
334 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, false)
336 // Give manage permissions
337 tmpfile, err = MakeTempCSVFile([][]string{{groupName, userEmail, "can_manage"}})
339 defer os.Remove(tmpfile.Name()) // clean up
340 s.cfg.Path = tmpfile.Name()
344 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, false)
345 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, false)
346 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, true)
349 // The absence of a user membership on the CSV file implies its removal
350 func (s *TestSuite) TestMembershipRemoval(c *C) {
351 localUserEmail := s.users[arvadostest.ActiveUserUUID].Email
352 localUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
353 remoteUserEmail := s.users[arvadostest.FederatedActiveUserUUID].Email
354 remoteUserUUID := s.users[arvadostest.FederatedActiveUserUUID].UUID
356 {"TestGroup1", localUserEmail},
357 {"TestGroup1", remoteUserEmail},
358 {"TestGroup2", localUserEmail},
359 {"TestGroup2", remoteUserEmail},
361 tmpfile, err := MakeTempCSVFile(data)
363 defer os.Remove(tmpfile.Name()) // clean up
364 s.cfg.Path = tmpfile.Name()
367 // Confirm that memberships exist
368 for _, groupName := range []string{"TestGroup1", "TestGroup2"} {
369 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
371 c.Assert(groupUUID, Not(Equals), "")
372 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, true)
373 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, true)
375 // New CSV with some previous membership missing
377 {"TestGroup1", localUserEmail},
378 {"TestGroup2", remoteUserEmail},
380 tmpfile2, err := MakeTempCSVFile(data)
382 defer os.Remove(tmpfile2.Name()) // clean up
383 s.cfg.Path = tmpfile2.Name()
386 // Confirm TestGroup1 memberships
387 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
389 c.Assert(groupUUID, Not(Equals), "")
390 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, true)
391 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, false)
392 // Confirm TestGroup1 memberships
393 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup2")
395 c.Assert(groupUUID, Not(Equals), "")
396 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, false)
397 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, true)
400 // If a group doesn't exist on the system, create it before adding users
401 func (s *TestSuite) TestAutoCreateGroupWhenNotExisting(c *C) {
402 groupName := "Testers"
403 // Confirm that group doesn't exist
404 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
406 c.Assert(groupUUID, Equals, "")
407 // Make a tmp CSV file
409 {groupName, s.users[arvadostest.ActiveUserUUID].Email},
411 tmpfile, err := MakeTempCSVFile(data)
413 defer os.Remove(tmpfile.Name()) // clean up
414 s.cfg.Path = tmpfile.Name()
417 // "Testers" group should now exist
418 groupUUID, err = RemoteGroupExists(s.cfg, groupName)
420 c.Assert(groupUUID, Not(Equals), "")
421 // active user should be a member
422 c.Assert(GroupMembershipExists(s.cfg.Client, arvadostest.ActiveUserUUID, groupUUID, "can_write"), Equals, true)
425 // Users listed on the file that don't exist on the system are ignored
426 func (s *TestSuite) TestIgnoreNonexistantUsers(c *C) {
427 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
428 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
429 // Confirm that group doesn't exist
430 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup4")
432 c.Assert(groupUUID, Equals, "")
433 // Create file & run command
435 {"TestGroup4", "nonexistantuser@unknowndomain.com"}, // Processed first
436 {"TestGroup4", activeUserEmail},
438 tmpfile, err := MakeTempCSVFile(data)
440 defer os.Remove(tmpfile.Name()) // clean up
441 s.cfg.Path = tmpfile.Name()
444 // Confirm that memberships exist
445 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup4")
447 c.Assert(groupUUID, Not(Equals), "")
448 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
451 // Entries with missing data are ignored.
452 func (s *TestSuite) TestIgnoreEmptyFields(c *C) {
453 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
454 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
455 // Confirm that group doesn't exist
456 for _, groupName := range []string{"TestGroup4", "TestGroup5"} {
457 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
459 c.Assert(groupUUID, Equals, "")
461 // Create file & run command
463 {"", activeUserEmail}, // Empty field
464 {"TestGroup5", ""}, // Empty field
465 {"TestGroup5", activeUserEmail, ""}, // Empty 3rd field: is optional but cannot be empty
466 {"TestGroup4", activeUserEmail},
468 tmpfile, err := MakeTempCSVFile(data)
470 defer os.Remove(tmpfile.Name()) // clean up
471 s.cfg.Path = tmpfile.Name()
474 // Confirm that records about TestGroup5 were skipped
475 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup5")
477 c.Assert(groupUUID, Equals, "")
478 // Confirm that membership exists
479 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup4")
481 c.Assert(groupUUID, Not(Equals), "")
482 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
485 // Instead of emails, use username as identifier
486 func (s *TestSuite) TestUseUsernames(c *C) {
487 activeUserName := s.users[arvadostest.ActiveUserUUID].Username
488 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
489 // Confirm that group doesn't exist
490 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
492 c.Assert(groupUUID, Equals, "")
493 // Create file & run command
495 {"TestGroup1", activeUserName},
497 tmpfile, err := MakeTempCSVFile(data)
499 defer os.Remove(tmpfile.Name()) // clean up
500 s.cfg.Path = tmpfile.Name()
501 s.cfg.UserID = "username"
504 // Confirm that memberships exist
505 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup1")
507 c.Assert(groupUUID, Not(Equals), "")
508 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
511 func (s *TestSuite) TestUseUsernamesWithCaseInsensitiveMatching(c *C) {
512 activeUserName := strings.ToUpper(s.users[arvadostest.ActiveUserUUID].Username)
513 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
514 // Confirm that group doesn't exist
515 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
517 c.Assert(groupUUID, Equals, "")
518 // Create file & run command
520 {"TestGroup1", activeUserName},
522 tmpfile, err := MakeTempCSVFile(data)
524 defer os.Remove(tmpfile.Name()) // clean up
525 s.cfg.Path = tmpfile.Name()
526 s.cfg.UserID = "username"
527 s.cfg.CaseInsensitive = true
530 // Confirm that memberships exist
531 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup1")
533 c.Assert(groupUUID, Not(Equals), "")
534 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)