+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package main
+
+import (
+ "net"
+ "os"
+ "path/filepath"
+
+ "git.arvados.org/arvados.git/sdk/go/arvados"
+ "git.arvados.org/arvados.git/sdk/go/arvadostest"
+ check "gopkg.in/check.v1"
+)
+
+var _ = check.Suite(&FederationSuite{})
+
+var origAPIHost, origAPIToken string
+
+type FederationSuite struct {
+ testClusters map[string]*arvadostest.TestCluster
+ oidcprovider *arvadostest.OIDCProvider
+}
+
+func (s *FederationSuite) SetUpSuite(c *check.C) {
+ origAPIHost = os.Getenv("ARVADOS_API_HOST")
+ origAPIToken = os.Getenv("ARVADOS_API_TOKEN")
+
+ cwd, _ := os.Getwd()
+
+ s.oidcprovider = arvadostest.NewOIDCProvider(c)
+ s.oidcprovider.AuthEmail = "user@example.com"
+ s.oidcprovider.AuthEmailVerified = true
+ s.oidcprovider.AuthName = "Example User"
+ s.oidcprovider.ValidClientID = "clientid"
+ s.oidcprovider.ValidClientSecret = "clientsecret"
+
+ s.testClusters = map[string]*arvadostest.TestCluster{
+ "z1111": nil,
+ "z2222": nil,
+ }
+ hostport := map[string]string{}
+ for id := range s.testClusters {
+ hostport[id] = func() string {
+ // TODO: Instead of expecting random ports on
+ // 127.0.0.11, 22 to be race-safe, try
+ // different 127.x.y.z until finding one that
+ // isn't in use.
+ ln, err := net.Listen("tcp", ":0")
+ c.Assert(err, check.IsNil)
+ ln.Close()
+ _, port, err := net.SplitHostPort(ln.Addr().String())
+ c.Assert(err, check.IsNil)
+ return "127.0.0." + id[3:] + ":" + port
+ }()
+ }
+ for id := range s.testClusters {
+ yaml := `Clusters:
+ ` + id + `:
+ Services:
+ Controller:
+ ExternalURL: https://` + hostport[id] + `
+ TLS:
+ Insecure: true
+ SystemLogs:
+ Format: text
+ RemoteClusters:
+ z1111:
+ Host: ` + hostport["z1111"] + `
+ Scheme: https
+ Insecure: true
+ Proxy: true
+ ActivateUsers: true
+`
+ if id != "z2222" {
+ yaml += ` z2222:
+ Host: ` + hostport["z2222"] + `
+ Scheme: https
+ Insecure: true
+ Proxy: true
+ ActivateUsers: true
+`
+ }
+ if id == "z1111" {
+ yaml += `
+ Login:
+ LoginCluster: z1111
+ OpenIDConnect:
+ Enable: true
+ Issuer: ` + s.oidcprovider.Issuer.URL + `
+ ClientID: ` + s.oidcprovider.ValidClientID + `
+ ClientSecret: ` + s.oidcprovider.ValidClientSecret + `
+ EmailClaim: email
+ EmailVerifiedClaim: email_verified
+`
+ } else {
+ yaml += `
+ Login:
+ LoginCluster: z1111
+`
+ }
+
+ tc, err := arvadostest.NewTestCluster(
+ filepath.Join(cwd, "..", ".."),
+ id, yaml, "127.0.0."+id[3:], c.Log)
+ c.Assert(err, check.IsNil)
+ s.testClusters[id] = tc
+ s.testClusters[id].Start()
+ }
+ for _, tc := range s.testClusters {
+ ok := tc.WaitReady()
+ c.Assert(ok, check.Equals, true)
+ }
+
+ // Activate user, make it admin.
+ conn1 := s.testClusters["z1111"].Conn()
+ rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+ userctx1, _, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
+ user1, err := conn1.UserGetCurrent(userctx1, arvados.GetOptions{})
+ c.Assert(err, check.IsNil)
+ c.Assert(user1.IsAdmin, check.Equals, false)
+ user1, err = conn1.UserUpdate(rootctx1, arvados.UpdateOptions{
+ UUID: user1.UUID,
+ Attrs: map[string]interface{}{
+ "is_admin": true,
+ },
+ })
+ c.Assert(err, check.IsNil)
+ c.Assert(user1.IsAdmin, check.Equals, true)
+}
+
+func (s *FederationSuite) TearDownSuite(c *check.C) {
+ for _, c := range s.testClusters {
+ c.Super.Stop()
+ }
+ _ = os.Setenv("ARVADOS_API_HOST", origAPIHost)
+ _ = os.Setenv("ARVADOS_API_TOKEN", origAPIToken)
+}
+
+func (s *FederationSuite) TestGroupSyncingOnFederatedCluster(c *check.C) {
+ // Get admin user's V2 token
+ conn1 := s.testClusters["z1111"].Conn()
+ rootctx1, _, _ := s.testClusters["z1111"].RootClients()
+ userctx1, _, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
+ user1Auth, err := conn1.APIClientAuthorizationCurrent(userctx1, arvados.GetOptions{})
+ c.Check(err, check.IsNil)
+ userV2Token := user1Auth.TokenV2()
+
+ // Get federated admin clients on z2222 to set up environment
+ conn2 := s.testClusters["z2222"].Conn()
+ userctx2, userac2, _ := s.testClusters["z2222"].ClientsWithToken(userV2Token)
+ user2, err := conn2.UserGetCurrent(userctx2, arvados.GetOptions{})
+ c.Check(err, check.IsNil)
+ c.Check(user2.IsAdmin, check.Equals, true)
+
+ // Set up environment for sync-groups using admin user credentials on z2222
+ err = os.Setenv("ARVADOS_API_HOST", userac2.APIHost)
+ c.Assert(err, check.IsNil)
+ err = os.Setenv("ARVADOS_API_TOKEN", userac2.AuthToken)
+ c.Assert(err, check.IsNil)
+
+ // Check that no parent group is created
+ gl := arvados.GroupList{}
+ params := arvados.ResourceListParams{
+ Filters: []arvados.Filter{{
+ Attr: "owner_uuid",
+ Operator: "=",
+ Operand: s.testClusters["z2222"].ClusterID + "-tpzed-000000000000000",
+ }, {
+ Attr: "name",
+ Operator: "=",
+ Operand: "Externally synchronized groups",
+ }},
+ }
+ err = userac2.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
+ c.Assert(err, check.IsNil)
+ c.Assert(gl.ItemsAvailable, check.Equals, 0)
+
+ // Set up config, confirm that the parent group was created
+ os.Args = []string{"cmd", "somefile.csv"}
+ config, err := GetConfig()
+ c.Assert(err, check.IsNil)
+ userac2.RequestAndDecode(&gl, "GET", "/arvados/v1/groups", nil, params)
+ c.Assert(gl.ItemsAvailable, check.Equals, 1)
+
+ // Run the tool with custom config
+ data := [][]string{
+ {"TestGroup1", user2.Email},
+ }
+ tmpfile, err := MakeTempCSVFile(data)
+ c.Assert(err, check.IsNil)
+ defer os.Remove(tmpfile.Name()) // clean up
+ config.Path = tmpfile.Name()
+ err = doMain(&config)
+ c.Assert(err, check.IsNil)
+ // Check the group was created correctly, and has the user as a member
+ groupUUID, err := RemoteGroupExists(&config, "TestGroup1")
+ c.Assert(err, check.IsNil)
+ c.Assert(groupUUID, check.Not(check.Equals), "")
+ c.Assert(GroupMembershipExists(config.Client, user2.UUID, groupUUID, "can_write"), check.Equals, true)
+}