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", "-case-insensitive", "/tmp/somefile.csv"}
191 err := ParseFlags(&cfg)
193 c.Check(cfg.Path, Equals, "/tmp/somefile.csv")
194 c.Check(cfg.Verbose, Equals, true)
195 c.Check(cfg.CaseInsensitive, Equals, true)
198 func (s *TestSuite) TestParseFlagsWithoutPositionalArgument(c *C) {
199 os.Args = []string{"cmd", "-verbose"}
200 err := ParseFlags(&ConfigParams{})
201 c.Assert(err, NotNil)
204 func (s *TestSuite) TestGetUserID(c *C) {
206 Email: "testuser@example.com",
207 Username: "Testuser",
209 email, err := GetUserID(u, "email")
211 c.Check(email, Equals, "testuser@example.com")
212 _, err = GetUserID(u, "bogus")
213 c.Assert(err, NotNil)
216 func (s *TestSuite) TestGetConfig(c *C) {
217 os.Args = []string{"cmd", "/tmp/somefile.csv"}
218 cfg, err := GetConfig()
220 c.Check(cfg.SysUserUUID, NotNil)
221 c.Check(cfg.Client, NotNil)
222 c.Check(cfg.ParentGroupUUID, NotNil)
223 c.Check(cfg.ParentGroupName, Equals, "Externally synchronized groups")
226 // Ignore leading & trailing spaces on group & users names
227 func (s *TestSuite) TestIgnoreSpaces(c *C) {
228 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
229 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
230 // Confirm that the groups don't exist
231 for _, groupName := range []string{"TestGroup1", "TestGroup2", "Test Group 3"} {
232 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
234 c.Assert(groupUUID, Equals, "")
237 {" TestGroup1", activeUserEmail},
238 {"TestGroup2 ", " " + activeUserEmail},
239 {" Test Group 3 ", activeUserEmail + " "},
241 tmpfile, err := MakeTempCSVFile(data)
243 defer os.Remove(tmpfile.Name()) // clean up
244 s.cfg.Path = tmpfile.Name()
247 // Check that 3 groups were created correctly, and have the active user as
249 for _, groupName := range []string{"TestGroup1", "TestGroup2", "Test Group 3"} {
250 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
252 c.Assert(groupUUID, Not(Equals), "")
253 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
257 // Error out when records have <2 or >3 records
258 func (s *TestSuite) TestWrongNumberOfFields(c *C) {
259 for _, testCase := range [][][]string{
261 {{"field1", "field2", "field3", "field4"}},
262 {{"field1", "field2", "field3", "field4", "field5"}},
264 tmpfile, err := MakeTempCSVFile(testCase)
266 defer os.Remove(tmpfile.Name())
267 s.cfg.Path = tmpfile.Name()
269 c.Assert(err, Not(IsNil))
273 // Check different membership permissions
274 func (s *TestSuite) TestMembershipLevels(c *C) {
275 userEmail := s.users[arvadostest.ActiveUserUUID].Email
276 userUUID := s.users[arvadostest.ActiveUserUUID].UUID
278 {"TestGroup1", userEmail, "can_read"},
279 {"TestGroup2", userEmail, "can_write"},
280 {"TestGroup3", userEmail, "can_manage"},
281 {"TestGroup4", userEmail, "invalid_permission"},
283 tmpfile, err := MakeTempCSVFile(data)
285 defer os.Remove(tmpfile.Name()) // clean up
286 s.cfg.Path = tmpfile.Name()
289 for _, record := range data {
290 groupName := record[0]
291 permLevel := record[2]
292 if permLevel != "invalid_permission" {
293 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
295 c.Assert(groupUUID, Not(Equals), "")
296 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, permLevel), Equals, true)
298 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
300 c.Assert(groupUUID, Equals, "")
305 // Check membership level change
306 func (s *TestSuite) TestMembershipLevelUpdate(c *C) {
307 userEmail := s.users[arvadostest.ActiveUserUUID].Email
308 userUUID := s.users[arvadostest.ActiveUserUUID].UUID
309 groupName := "TestGroup1"
310 // Give read permissions
311 tmpfile, err := MakeTempCSVFile([][]string{{groupName, userEmail, "can_read"}})
313 defer os.Remove(tmpfile.Name()) // clean up
314 s.cfg.Path = tmpfile.Name()
318 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
320 c.Assert(groupUUID, Not(Equals), "")
321 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, true)
322 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, false)
323 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, false)
325 // Give write permissions
326 tmpfile, err = MakeTempCSVFile([][]string{{groupName, userEmail, "can_write"}})
328 defer os.Remove(tmpfile.Name()) // clean up
329 s.cfg.Path = tmpfile.Name()
333 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, false)
334 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, true)
335 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, false)
337 // Give manage permissions
338 tmpfile, err = MakeTempCSVFile([][]string{{groupName, userEmail, "can_manage"}})
340 defer os.Remove(tmpfile.Name()) // clean up
341 s.cfg.Path = tmpfile.Name()
345 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_read"), Equals, false)
346 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_write"), Equals, false)
347 c.Assert(GroupMembershipExists(s.cfg.Client, userUUID, groupUUID, "can_manage"), Equals, true)
350 // The absence of a user membership on the CSV file implies its removal
351 func (s *TestSuite) TestMembershipRemoval(c *C) {
352 localUserEmail := s.users[arvadostest.ActiveUserUUID].Email
353 localUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
354 remoteUserEmail := s.users[arvadostest.FederatedActiveUserUUID].Email
355 remoteUserUUID := s.users[arvadostest.FederatedActiveUserUUID].UUID
357 {"TestGroup1", localUserEmail},
358 {"TestGroup1", remoteUserEmail},
359 {"TestGroup2", localUserEmail},
360 {"TestGroup2", remoteUserEmail},
362 tmpfile, err := MakeTempCSVFile(data)
364 defer os.Remove(tmpfile.Name()) // clean up
365 s.cfg.Path = tmpfile.Name()
368 // Confirm that memberships exist
369 for _, groupName := range []string{"TestGroup1", "TestGroup2"} {
370 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
372 c.Assert(groupUUID, Not(Equals), "")
373 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, true)
374 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, true)
376 // New CSV with some previous membership missing
378 {"TestGroup1", localUserEmail},
379 {"TestGroup2", remoteUserEmail},
381 tmpfile2, err := MakeTempCSVFile(data)
383 defer os.Remove(tmpfile2.Name()) // clean up
384 s.cfg.Path = tmpfile2.Name()
387 // Confirm TestGroup1 memberships
388 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
390 c.Assert(groupUUID, Not(Equals), "")
391 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, true)
392 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, false)
393 // Confirm TestGroup1 memberships
394 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup2")
396 c.Assert(groupUUID, Not(Equals), "")
397 c.Assert(GroupMembershipExists(s.cfg.Client, localUserUUID, groupUUID, "can_write"), Equals, false)
398 c.Assert(GroupMembershipExists(s.cfg.Client, remoteUserUUID, groupUUID, "can_write"), Equals, true)
401 // If a group doesn't exist on the system, create it before adding users
402 func (s *TestSuite) TestAutoCreateGroupWhenNotExisting(c *C) {
403 groupName := "Testers"
404 // Confirm that group doesn't exist
405 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
407 c.Assert(groupUUID, Equals, "")
408 // Make a tmp CSV file
410 {groupName, s.users[arvadostest.ActiveUserUUID].Email},
412 tmpfile, err := MakeTempCSVFile(data)
414 defer os.Remove(tmpfile.Name()) // clean up
415 s.cfg.Path = tmpfile.Name()
418 // "Testers" group should now exist
419 groupUUID, err = RemoteGroupExists(s.cfg, groupName)
421 c.Assert(groupUUID, Not(Equals), "")
422 // active user should be a member
423 c.Assert(GroupMembershipExists(s.cfg.Client, arvadostest.ActiveUserUUID, groupUUID, "can_write"), Equals, true)
426 // Users listed on the file that don't exist on the system are ignored
427 func (s *TestSuite) TestIgnoreNonexistantUsers(c *C) {
428 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
429 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
430 // Confirm that group doesn't exist
431 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup4")
433 c.Assert(groupUUID, Equals, "")
434 // Create file & run command
436 {"TestGroup4", "nonexistantuser@unknowndomain.com"}, // Processed first
437 {"TestGroup4", activeUserEmail},
439 tmpfile, err := MakeTempCSVFile(data)
441 defer os.Remove(tmpfile.Name()) // clean up
442 s.cfg.Path = tmpfile.Name()
445 // Confirm that memberships exist
446 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup4")
448 c.Assert(groupUUID, Not(Equals), "")
449 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
452 // Entries with missing data are ignored.
453 func (s *TestSuite) TestIgnoreEmptyFields(c *C) {
454 activeUserEmail := s.users[arvadostest.ActiveUserUUID].Email
455 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
456 // Confirm that group doesn't exist
457 for _, groupName := range []string{"TestGroup4", "TestGroup5"} {
458 groupUUID, err := RemoteGroupExists(s.cfg, groupName)
460 c.Assert(groupUUID, Equals, "")
462 // Create file & run command
464 {"", activeUserEmail}, // Empty field
465 {"TestGroup5", ""}, // Empty field
466 {"TestGroup5", activeUserEmail, ""}, // Empty 3rd field: is optional but cannot be empty
467 {"TestGroup4", activeUserEmail},
469 tmpfile, err := MakeTempCSVFile(data)
471 defer os.Remove(tmpfile.Name()) // clean up
472 s.cfg.Path = tmpfile.Name()
475 // Confirm that records about TestGroup5 were skipped
476 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup5")
478 c.Assert(groupUUID, Equals, "")
479 // Confirm that membership exists
480 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup4")
482 c.Assert(groupUUID, Not(Equals), "")
483 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
486 // Instead of emails, use username as identifier
487 func (s *TestSuite) TestUseUsernames(c *C) {
488 activeUserName := s.users[arvadostest.ActiveUserUUID].Username
489 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
490 // Confirm that group doesn't exist
491 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
493 c.Assert(groupUUID, Equals, "")
494 // Create file & run command
496 {"TestGroup1", activeUserName},
498 tmpfile, err := MakeTempCSVFile(data)
500 defer os.Remove(tmpfile.Name()) // clean up
501 s.cfg.Path = tmpfile.Name()
502 s.cfg.UserID = "username"
505 // Confirm that memberships exist
506 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup1")
508 c.Assert(groupUUID, Not(Equals), "")
509 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
512 func (s *TestSuite) TestUseUsernamesWithCaseInsensitiveMatching(c *C) {
513 activeUserName := strings.ToUpper(s.users[arvadostest.ActiveUserUUID].Username)
514 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
515 // Confirm that group doesn't exist
516 groupUUID, err := RemoteGroupExists(s.cfg, "TestGroup1")
518 c.Assert(groupUUID, Equals, "")
519 // Create file & run command
521 {"TestGroup1", activeUserName},
523 tmpfile, err := MakeTempCSVFile(data)
525 defer os.Remove(tmpfile.Name()) // clean up
526 s.cfg.Path = tmpfile.Name()
527 s.cfg.UserID = "username"
528 s.cfg.CaseInsensitive = true
531 // Confirm that memberships exist
532 groupUUID, err = RemoteGroupExists(s.cfg, "TestGroup1")
534 c.Assert(groupUUID, Not(Equals), "")
535 c.Assert(GroupMembershipExists(s.cfg.Client, activeUserUUID, groupUUID, "can_write"), Equals, true)
538 func (s *TestSuite) TestUsernamesCaseInsensitiveCollision(c *C) {
539 activeUserName := s.users[arvadostest.ActiveUserUUID].Username
540 activeUserUUID := s.users[arvadostest.ActiveUserUUID].UUID
543 nuUsername := strings.ToUpper(activeUserName)
544 err := s.cfg.Client.RequestAndDecode(&nu, "POST", "/arvados/v1/users", nil, map[string]interface{}{
545 "user": map[string]string{
546 "username": nuUsername,
551 // Manually remove non-fixture user because /database/reset fails otherwise
552 defer s.cfg.Client.RequestAndDecode(nil, "DELETE", "/arvados/v1/users/"+nu.UUID, nil, nil)
554 c.Assert(nu.Username, Equals, nuUsername)
555 c.Assert(nu.UUID, Not(Equals), activeUserUUID)
556 c.Assert(nu.Username, Not(Equals), activeUserName)
559 {"SomeGroup", activeUserName},
561 tmpfile, err := MakeTempCSVFile(data)
563 defer os.Remove(tmpfile.Name()) // clean up
565 s.cfg.Path = tmpfile.Name()
566 s.cfg.UserID = "username"
567 s.cfg.CaseInsensitive = true
569 // Should get an error because of "ACTIVE" and "Active" usernames
570 c.Assert(err, NotNil)
571 c.Assert(err, ErrorMatches, ".*case insensitive collision.*")