17171: Merge branch 'master' into 17171-pysdk-timeout-fix
[arvados.git] / lib / controller / integration_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package controller
6
7 import (
8         "bytes"
9         "context"
10         "database/sql"
11         "encoding/json"
12         "fmt"
13         "io"
14         "io/ioutil"
15         "math"
16         "net"
17         "net/http"
18         "os"
19         "os/exec"
20         "path/filepath"
21         "strconv"
22         "strings"
23
24         "git.arvados.org/arvados.git/lib/boot"
25         "git.arvados.org/arvados.git/lib/config"
26         "git.arvados.org/arvados.git/sdk/go/arvados"
27         "git.arvados.org/arvados.git/sdk/go/arvadostest"
28         "git.arvados.org/arvados.git/sdk/go/ctxlog"
29         check "gopkg.in/check.v1"
30 )
31
32 var _ = check.Suite(&IntegrationSuite{})
33
34 type IntegrationSuite struct {
35         testClusters map[string]*boot.TestCluster
36         oidcprovider *arvadostest.OIDCProvider
37 }
38
39 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
40         cwd, _ := os.Getwd()
41
42         s.oidcprovider = arvadostest.NewOIDCProvider(c)
43         s.oidcprovider.AuthEmail = "user@example.com"
44         s.oidcprovider.AuthEmailVerified = true
45         s.oidcprovider.AuthName = "Example User"
46         s.oidcprovider.ValidClientID = "clientid"
47         s.oidcprovider.ValidClientSecret = "clientsecret"
48
49         s.testClusters = map[string]*boot.TestCluster{
50                 "z1111": nil,
51                 "z2222": nil,
52                 "z3333": nil,
53         }
54         hostport := map[string]string{}
55         for id := range s.testClusters {
56                 hostport[id] = func() string {
57                         // TODO: Instead of expecting random ports on
58                         // 127.0.0.11, 22, 33 to be race-safe, try
59                         // different 127.x.y.z until finding one that
60                         // isn't in use.
61                         ln, err := net.Listen("tcp", ":0")
62                         c.Assert(err, check.IsNil)
63                         ln.Close()
64                         _, port, err := net.SplitHostPort(ln.Addr().String())
65                         c.Assert(err, check.IsNil)
66                         return "127.0.0." + id[3:] + ":" + port
67                 }()
68         }
69         for id := range s.testClusters {
70                 yaml := `Clusters:
71   ` + id + `:
72     Services:
73       Controller:
74         ExternalURL: https://` + hostport[id] + `
75     TLS:
76       Insecure: true
77     SystemLogs:
78       Format: text
79     RemoteClusters:
80       z1111:
81         Host: ` + hostport["z1111"] + `
82         Scheme: https
83         Insecure: true
84         Proxy: true
85         ActivateUsers: true
86 `
87                 if id != "z2222" {
88                         yaml += `      z2222:
89         Host: ` + hostport["z2222"] + `
90         Scheme: https
91         Insecure: true
92         Proxy: true
93         ActivateUsers: true
94 `
95                 }
96                 if id != "z3333" {
97                         yaml += `      z3333:
98         Host: ` + hostport["z3333"] + `
99         Scheme: https
100         Insecure: true
101         Proxy: true
102         ActivateUsers: true
103 `
104                 }
105                 if id == "z1111" {
106                         yaml += `
107     Login:
108       LoginCluster: z1111
109       OpenIDConnect:
110         Enable: true
111         Issuer: ` + s.oidcprovider.Issuer.URL + `
112         ClientID: ` + s.oidcprovider.ValidClientID + `
113         ClientSecret: ` + s.oidcprovider.ValidClientSecret + `
114         EmailClaim: email
115         EmailVerifiedClaim: email_verified
116 `
117                 } else {
118                         yaml += `
119     Login:
120       LoginCluster: z1111
121 `
122                 }
123
124                 loader := config.NewLoader(bytes.NewBufferString(yaml), ctxlog.TestLogger(c))
125                 loader.Path = "-"
126                 loader.SkipLegacy = true
127                 loader.SkipAPICalls = true
128                 cfg, err := loader.Load()
129                 c.Assert(err, check.IsNil)
130                 tc := boot.NewTestCluster(
131                         filepath.Join(cwd, "..", ".."),
132                         id, cfg, "127.0.0."+id[3:], c.Log)
133                 s.testClusters[id] = tc
134                 s.testClusters[id].Start()
135         }
136         for _, tc := range s.testClusters {
137                 ok := tc.WaitReady()
138                 c.Assert(ok, check.Equals, true)
139         }
140 }
141
142 func (s *IntegrationSuite) TearDownSuite(c *check.C) {
143         for _, c := range s.testClusters {
144                 c.Super.Stop()
145         }
146 }
147
148 func (s *IntegrationSuite) TestGetCollectionByPDH(c *check.C) {
149         conn1 := s.testClusters["z1111"].Conn()
150         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
151         conn3 := s.testClusters["z3333"].Conn()
152         userctx1, ac1, kc1, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
153
154         // Create the collection to find its PDH (but don't save it
155         // anywhere yet)
156         var coll1 arvados.Collection
157         fs1, err := coll1.FileSystem(ac1, kc1)
158         c.Assert(err, check.IsNil)
159         f, err := fs1.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
160         c.Assert(err, check.IsNil)
161         _, err = io.WriteString(f, "IntegrationSuite.TestGetCollectionByPDH")
162         c.Assert(err, check.IsNil)
163         err = f.Close()
164         c.Assert(err, check.IsNil)
165         mtxt, err := fs1.MarshalManifest(".")
166         c.Assert(err, check.IsNil)
167         pdh := arvados.PortableDataHash(mtxt)
168
169         // Looking up the PDH before saving returns 404 if cycle
170         // detection is working.
171         _, err = conn1.CollectionGet(userctx1, arvados.GetOptions{UUID: pdh})
172         c.Assert(err, check.ErrorMatches, `.*404 Not Found.*`)
173
174         // Save the collection on cluster z1111.
175         coll1, err = conn1.CollectionCreate(userctx1, arvados.CreateOptions{Attrs: map[string]interface{}{
176                 "manifest_text": mtxt,
177         }})
178         c.Assert(err, check.IsNil)
179
180         // Retrieve the collection from cluster z3333.
181         coll, err := conn3.CollectionGet(userctx1, arvados.GetOptions{UUID: pdh})
182         c.Check(err, check.IsNil)
183         c.Check(coll.PortableDataHash, check.Equals, pdh)
184 }
185
186 func (s *IntegrationSuite) TestS3WithFederatedToken(c *check.C) {
187         if _, err := exec.LookPath("s3cmd"); err != nil {
188                 c.Skip("s3cmd not in PATH")
189                 return
190         }
191
192         testText := "IntegrationSuite.TestS3WithFederatedToken"
193
194         conn1 := s.testClusters["z1111"].Conn()
195         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
196         userctx1, ac1, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
197         conn3 := s.testClusters["z3333"].Conn()
198
199         createColl := func(clusterID string) arvados.Collection {
200                 _, ac, kc := s.testClusters[clusterID].ClientsWithToken(ac1.AuthToken)
201                 var coll arvados.Collection
202                 fs, err := coll.FileSystem(ac, kc)
203                 c.Assert(err, check.IsNil)
204                 f, err := fs.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
205                 c.Assert(err, check.IsNil)
206                 _, err = io.WriteString(f, testText)
207                 c.Assert(err, check.IsNil)
208                 err = f.Close()
209                 c.Assert(err, check.IsNil)
210                 mtxt, err := fs.MarshalManifest(".")
211                 c.Assert(err, check.IsNil)
212                 coll, err = s.testClusters[clusterID].Conn().CollectionCreate(userctx1, arvados.CreateOptions{Attrs: map[string]interface{}{
213                         "manifest_text": mtxt,
214                 }})
215                 c.Assert(err, check.IsNil)
216                 return coll
217         }
218
219         for _, trial := range []struct {
220                 clusterID string // create the collection on this cluster (then use z3333 to access it)
221                 token     string
222         }{
223                 // Try the hardest test first: z3333 hasn't seen
224                 // z1111's token yet, and we're just passing the
225                 // opaque secret part, so z3333 has to guess that it
226                 // belongs to z1111.
227                 {"z1111", strings.Split(ac1.AuthToken, "/")[2]},
228                 {"z3333", strings.Split(ac1.AuthToken, "/")[2]},
229                 {"z1111", strings.Replace(ac1.AuthToken, "/", "_", -1)},
230                 {"z3333", strings.Replace(ac1.AuthToken, "/", "_", -1)},
231         } {
232                 c.Logf("================ %v", trial)
233                 coll := createColl(trial.clusterID)
234
235                 cfgjson, err := conn3.ConfigGet(userctx1)
236                 c.Assert(err, check.IsNil)
237                 var cluster arvados.Cluster
238                 err = json.Unmarshal(cfgjson, &cluster)
239                 c.Assert(err, check.IsNil)
240
241                 c.Logf("TokenV2 is %s", ac1.AuthToken)
242                 host := cluster.Services.WebDAV.ExternalURL.Host
243                 s3args := []string{
244                         "--ssl", "--no-check-certificate",
245                         "--host=" + host, "--host-bucket=" + host,
246                         "--access_key=" + trial.token, "--secret_key=" + trial.token,
247                 }
248                 buf, err := exec.Command("s3cmd", append(s3args, "ls", "s3://"+coll.UUID)...).CombinedOutput()
249                 c.Check(err, check.IsNil)
250                 c.Check(string(buf), check.Matches, `.* `+fmt.Sprintf("%d", len(testText))+` +s3://`+coll.UUID+`/test.txt\n`)
251
252                 buf, _ = exec.Command("s3cmd", append(s3args, "get", "s3://"+coll.UUID+"/test.txt", c.MkDir()+"/tmpfile")...).CombinedOutput()
253                 // Command fails because we don't return Etag header.
254                 flen := strconv.Itoa(len(testText))
255                 c.Check(string(buf), check.Matches, `(?ms).*`+flen+` (bytes in|of `+flen+`).*`)
256         }
257 }
258
259 func (s *IntegrationSuite) TestGetCollectionAsAnonymous(c *check.C) {
260         conn1 := s.testClusters["z1111"].Conn()
261         conn3 := s.testClusters["z3333"].Conn()
262         rootctx1, rootac1, rootkc1 := s.testClusters["z1111"].RootClients()
263         anonctx3, anonac3, _ := s.testClusters["z3333"].AnonymousClients()
264
265         // Make sure anonymous token was set
266         c.Assert(anonac3.AuthToken, check.Not(check.Equals), "")
267
268         // Create the collection to find its PDH (but don't save it
269         // anywhere yet)
270         var coll1 arvados.Collection
271         fs1, err := coll1.FileSystem(rootac1, rootkc1)
272         c.Assert(err, check.IsNil)
273         f, err := fs1.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
274         c.Assert(err, check.IsNil)
275         _, err = io.WriteString(f, "IntegrationSuite.TestGetCollectionAsAnonymous")
276         c.Assert(err, check.IsNil)
277         err = f.Close()
278         c.Assert(err, check.IsNil)
279         mtxt, err := fs1.MarshalManifest(".")
280         c.Assert(err, check.IsNil)
281         pdh := arvados.PortableDataHash(mtxt)
282
283         // Save the collection on cluster z1111.
284         coll1, err = conn1.CollectionCreate(rootctx1, arvados.CreateOptions{Attrs: map[string]interface{}{
285                 "manifest_text": mtxt,
286         }})
287         c.Assert(err, check.IsNil)
288
289         // Share it with the anonymous users group.
290         var outLink arvados.Link
291         err = rootac1.RequestAndDecode(&outLink, "POST", "/arvados/v1/links", nil,
292                 map[string]interface{}{"link": map[string]interface{}{
293                         "link_class": "permission",
294                         "name":       "can_read",
295                         "tail_uuid":  "z1111-j7d0g-anonymouspublic",
296                         "head_uuid":  coll1.UUID,
297                 },
298                 })
299         c.Check(err, check.IsNil)
300
301         // Current user should be z3 anonymous user
302         outUser, err := anonac3.CurrentUser()
303         c.Check(err, check.IsNil)
304         c.Check(outUser.UUID, check.Equals, "z3333-tpzed-anonymouspublic")
305
306         // Get the token uuid
307         var outAuth arvados.APIClientAuthorization
308         err = anonac3.RequestAndDecode(&outAuth, "GET", "/arvados/v1/api_client_authorizations/current", nil, nil)
309         c.Check(err, check.IsNil)
310
311         // Make a v2 token of the z3 anonymous user, and use it on z1
312         _, anonac1, _ := s.testClusters["z1111"].ClientsWithToken(outAuth.TokenV2())
313         outUser2, err := anonac1.CurrentUser()
314         c.Check(err, check.IsNil)
315         // z3 anonymous user will be mapped to the z1 anonymous user
316         c.Check(outUser2.UUID, check.Equals, "z1111-tpzed-anonymouspublic")
317
318         // Retrieve the collection (which is on z1) using anonymous from cluster z3333.
319         coll, err := conn3.CollectionGet(anonctx3, arvados.GetOptions{UUID: coll1.UUID})
320         c.Check(err, check.IsNil)
321         c.Check(coll.PortableDataHash, check.Equals, pdh)
322 }
323
324 // Get a token from the login cluster (z1111), use it to submit a
325 // container request on z2222.
326 func (s *IntegrationSuite) TestCreateContainerRequestWithFedToken(c *check.C) {
327         conn1 := s.testClusters["z1111"].Conn()
328         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
329         _, ac1, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
330
331         // Use ac2 to get the discovery doc with a blank token, so the
332         // SDK doesn't magically pass the z1111 token to z2222 before
333         // we're ready to start our test.
334         _, ac2, _ := s.testClusters["z2222"].ClientsWithToken("")
335         var dd map[string]interface{}
336         err := ac2.RequestAndDecode(&dd, "GET", "discovery/v1/apis/arvados/v1/rest", nil, nil)
337         c.Assert(err, check.IsNil)
338
339         var (
340                 body bytes.Buffer
341                 req  *http.Request
342                 resp *http.Response
343                 u    arvados.User
344                 cr   arvados.ContainerRequest
345         )
346         json.NewEncoder(&body).Encode(map[string]interface{}{
347                 "container_request": map[string]interface{}{
348                         "command":         []string{"echo"},
349                         "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
350                         "cwd":             "/",
351                         "output_path":     "/",
352                 },
353         })
354         ac2.AuthToken = ac1.AuthToken
355
356         c.Log("...post CR with good (but not yet cached) token")
357         cr = arvados.ContainerRequest{}
358         req, err = http.NewRequest("POST", "https://"+ac2.APIHost+"/arvados/v1/container_requests", bytes.NewReader(body.Bytes()))
359         c.Assert(err, check.IsNil)
360         req.Header.Set("Content-Type", "application/json")
361         err = ac2.DoAndDecode(&cr, req)
362         c.Assert(err, check.IsNil)
363         c.Logf("err == %#v", err)
364
365         c.Log("...get user with good token")
366         u = arvados.User{}
367         req, err = http.NewRequest("GET", "https://"+ac2.APIHost+"/arvados/v1/users/current", nil)
368         c.Assert(err, check.IsNil)
369         err = ac2.DoAndDecode(&u, req)
370         c.Check(err, check.IsNil)
371         c.Check(u.UUID, check.Matches, "z1111-tpzed-.*")
372
373         c.Log("...post CR with good cached token")
374         cr = arvados.ContainerRequest{}
375         req, err = http.NewRequest("POST", "https://"+ac2.APIHost+"/arvados/v1/container_requests", bytes.NewReader(body.Bytes()))
376         c.Assert(err, check.IsNil)
377         req.Header.Set("Content-Type", "application/json")
378         err = ac2.DoAndDecode(&cr, req)
379         c.Check(err, check.IsNil)
380         c.Check(cr.UUID, check.Matches, "z2222-.*")
381
382         c.Log("...post with good cached token ('OAuth2 ...')")
383         cr = arvados.ContainerRequest{}
384         req, err = http.NewRequest("POST", "https://"+ac2.APIHost+"/arvados/v1/container_requests", bytes.NewReader(body.Bytes()))
385         c.Assert(err, check.IsNil)
386         req.Header.Set("Content-Type", "application/json")
387         req.Header.Set("Authorization", "OAuth2 "+ac2.AuthToken)
388         resp, err = arvados.InsecureHTTPClient.Do(req)
389         c.Assert(err, check.IsNil)
390         err = json.NewDecoder(resp.Body).Decode(&cr)
391         c.Check(err, check.IsNil)
392         c.Check(cr.UUID, check.Matches, "z2222-.*")
393 }
394
395 func (s *IntegrationSuite) TestCreateContainerRequestWithBadToken(c *check.C) {
396         conn1 := s.testClusters["z1111"].Conn()
397         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
398         _, ac1, _, au := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
399
400         tests := []struct {
401                 name         string
402                 token        string
403                 expectedCode int
404         }{
405                 {"Good token", ac1.AuthToken, http.StatusOK},
406                 {"Bogus token", "abcdef", http.StatusUnauthorized},
407                 {"v1-looking token", "badtoken00badtoken00badtoken00badtoken00b", http.StatusUnauthorized},
408                 {"v2-looking token", "v2/" + au.UUID + "/badtoken00badtoken00badtoken00badtoken00b", http.StatusUnauthorized},
409         }
410
411         body, _ := json.Marshal(map[string]interface{}{
412                 "container_request": map[string]interface{}{
413                         "command":         []string{"echo"},
414                         "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
415                         "cwd":             "/",
416                         "output_path":     "/",
417                 },
418         })
419
420         for _, tt := range tests {
421                 c.Log(c.TestName() + " " + tt.name)
422                 ac1.AuthToken = tt.token
423                 req, err := http.NewRequest("POST", "https://"+ac1.APIHost+"/arvados/v1/container_requests", bytes.NewReader(body))
424                 c.Assert(err, check.IsNil)
425                 req.Header.Set("Content-Type", "application/json")
426                 resp, err := ac1.Do(req)
427                 c.Assert(err, check.IsNil)
428                 c.Assert(resp.StatusCode, check.Equals, tt.expectedCode)
429         }
430 }
431
432 // We test the direct access to the database
433 // normally an integration test would not have a database access, but  in this case we need
434 // to test tokens that are secret, so there is no API response that will give them back
435 func (s *IntegrationSuite) dbConn(c *check.C, clusterID string) (*sql.DB, *sql.Conn) {
436         ctx := context.Background()
437         db, err := sql.Open("postgres", s.testClusters[clusterID].Super.Cluster().PostgreSQL.Connection.String())
438         c.Assert(err, check.IsNil)
439
440         conn, err := db.Conn(ctx)
441         c.Assert(err, check.IsNil)
442
443         rows, err := conn.ExecContext(ctx, `SELECT 1`)
444         c.Assert(err, check.IsNil)
445         n, err := rows.RowsAffected()
446         c.Assert(err, check.IsNil)
447         c.Assert(n, check.Equals, int64(1))
448         return db, conn
449 }
450
451 // TestRuntimeTokenInCR will test several different tokens in the runtime attribute
452 // and check the expected results accessing directly to the database if needed.
453 func (s *IntegrationSuite) TestRuntimeTokenInCR(c *check.C) {
454         db, dbconn := s.dbConn(c, "z1111")
455         defer db.Close()
456         defer dbconn.Close()
457         conn1 := s.testClusters["z1111"].Conn()
458         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
459         userctx1, ac1, _, au := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
460
461         tests := []struct {
462                 name                 string
463                 token                string
464                 expectAToGetAValidCR bool
465                 expectedToken        *string
466         }{
467                 {"Good token z1111 user", ac1.AuthToken, true, &ac1.AuthToken},
468                 {"Bogus token", "abcdef", false, nil},
469                 {"v1-looking token", "badtoken00badtoken00badtoken00badtoken00b", false, nil},
470                 {"v2-looking token", "v2/" + au.UUID + "/badtoken00badtoken00badtoken00badtoken00b", false, nil},
471         }
472
473         for _, tt := range tests {
474                 c.Log(c.TestName() + " " + tt.name)
475
476                 rq := map[string]interface{}{
477                         "command":         []string{"echo"},
478                         "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
479                         "cwd":             "/",
480                         "output_path":     "/",
481                         "runtime_token":   tt.token,
482                 }
483                 cr, err := conn1.ContainerRequestCreate(userctx1, arvados.CreateOptions{Attrs: rq})
484                 if tt.expectAToGetAValidCR {
485                         c.Check(err, check.IsNil)
486                         c.Check(cr, check.NotNil)
487                         c.Check(cr.UUID, check.Not(check.Equals), "")
488                 }
489
490                 if tt.expectedToken == nil {
491                         continue
492                 }
493
494                 c.Logf("cr.UUID: %s", cr.UUID)
495                 row := dbconn.QueryRowContext(rootctx1, `SELECT runtime_token from container_requests where uuid=$1`, cr.UUID)
496                 c.Check(row, check.NotNil)
497                 var token sql.NullString
498                 row.Scan(&token)
499                 if c.Check(token.Valid, check.Equals, true) {
500                         c.Check(token.String, check.Equals, *tt.expectedToken)
501                 }
502         }
503 }
504
505 // TestIntermediateCluster will send a container request to
506 // one cluster with another cluster as the destination
507 // and check the tokens are being handled properly
508 func (s *IntegrationSuite) TestIntermediateCluster(c *check.C) {
509         conn1 := s.testClusters["z1111"].Conn()
510         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
511         uctx1, ac1, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, "user@example.com", true)
512
513         tests := []struct {
514                 name                 string
515                 token                string
516                 expectedRuntimeToken string
517                 expectedUUIDprefix   string
518         }{
519                 {"Good token z1111 user sending a CR to z2222", ac1.AuthToken, "", "z2222-xvhdp-"},
520         }
521
522         for _, tt := range tests {
523                 c.Log(c.TestName() + " " + tt.name)
524                 rq := map[string]interface{}{
525                         "command":         []string{"echo"},
526                         "container_image": "d41d8cd98f00b204e9800998ecf8427e+0",
527                         "cwd":             "/",
528                         "output_path":     "/",
529                         "runtime_token":   tt.token,
530                 }
531                 cr, err := conn1.ContainerRequestCreate(uctx1, arvados.CreateOptions{ClusterID: "z2222", Attrs: rq})
532
533                 c.Check(err, check.IsNil)
534                 c.Check(strings.HasPrefix(cr.UUID, tt.expectedUUIDprefix), check.Equals, true)
535                 c.Check(cr.RuntimeToken, check.Equals, tt.expectedRuntimeToken)
536         }
537 }
538
539 // Test for bug #16263
540 func (s *IntegrationSuite) TestListUsers(c *check.C) {
541         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
542         conn1 := s.testClusters["z1111"].Conn()
543         conn3 := s.testClusters["z3333"].Conn()
544         userctx1, _, _, _ := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
545
546         // Make sure LoginCluster is properly configured
547         for cls := range s.testClusters {
548                 c.Check(
549                         s.testClusters[cls].Config.Clusters[cls].Login.LoginCluster,
550                         check.Equals, "z1111",
551                         check.Commentf("incorrect LoginCluster config on cluster %q", cls))
552         }
553         // Make sure z1111 has users with NULL usernames
554         lst, err := conn1.UserList(rootctx1, arvados.ListOptions{
555                 Limit: math.MaxInt64, // check that large limit works (see #16263)
556         })
557         nullUsername := false
558         c.Assert(err, check.IsNil)
559         c.Assert(len(lst.Items), check.Not(check.Equals), 0)
560         for _, user := range lst.Items {
561                 if user.Username == "" {
562                         nullUsername = true
563                 }
564         }
565         c.Assert(nullUsername, check.Equals, true)
566
567         user1, err := conn1.UserGetCurrent(userctx1, arvados.GetOptions{})
568         c.Assert(err, check.IsNil)
569         c.Check(user1.IsActive, check.Equals, true)
570
571         // Ask for the user list on z3333 using z1111's system root token
572         lst, err = conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
573         c.Assert(err, check.IsNil)
574         found := false
575         for _, user := range lst.Items {
576                 if user.UUID == user1.UUID {
577                         c.Check(user.IsActive, check.Equals, true)
578                         found = true
579                         break
580                 }
581         }
582         c.Check(found, check.Equals, true)
583
584         // Deactivate user acct on z1111
585         _, err = conn1.UserUnsetup(rootctx1, arvados.GetOptions{UUID: user1.UUID})
586         c.Assert(err, check.IsNil)
587
588         // Get user list from z3333, check the returned z1111 user is
589         // deactivated
590         lst, err = conn3.UserList(rootctx1, arvados.ListOptions{Limit: -1})
591         c.Assert(err, check.IsNil)
592         found = false
593         for _, user := range lst.Items {
594                 if user.UUID == user1.UUID {
595                         c.Check(user.IsActive, check.Equals, false)
596                         found = true
597                         break
598                 }
599         }
600         c.Check(found, check.Equals, true)
601
602         // Deactivated user can see is_active==false via "get current
603         // user" API
604         user1, err = conn3.UserGetCurrent(userctx1, arvados.GetOptions{})
605         c.Assert(err, check.IsNil)
606         c.Check(user1.IsActive, check.Equals, false)
607 }
608
609 func (s *IntegrationSuite) TestSetupUserWithVM(c *check.C) {
610         conn1 := s.testClusters["z1111"].Conn()
611         conn3 := s.testClusters["z3333"].Conn()
612         rootctx1, rootac1, _ := s.testClusters["z1111"].RootClients()
613
614         // Create user on LoginCluster z1111
615         _, _, _, user := s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
616
617         // Make a new root token (because rootClients() uses SystemRootToken)
618         var outAuth arvados.APIClientAuthorization
619         err := rootac1.RequestAndDecode(&outAuth, "POST", "/arvados/v1/api_client_authorizations", nil, nil)
620         c.Check(err, check.IsNil)
621
622         // Make a v2 root token to communicate with z3333
623         rootctx3, rootac3, _ := s.testClusters["z3333"].ClientsWithToken(outAuth.TokenV2())
624
625         // Create VM on z3333
626         var outVM arvados.VirtualMachine
627         err = rootac3.RequestAndDecode(&outVM, "POST", "/arvados/v1/virtual_machines", nil,
628                 map[string]interface{}{"virtual_machine": map[string]interface{}{
629                         "hostname": "example",
630                 },
631                 })
632         c.Check(outVM.UUID[0:5], check.Equals, "z3333")
633         c.Check(err, check.IsNil)
634
635         // Make sure z3333 user list is up to date
636         _, err = conn3.UserList(rootctx3, arvados.ListOptions{Limit: 1000})
637         c.Check(err, check.IsNil)
638
639         // Try to set up user on z3333 with the VM
640         _, err = conn3.UserSetup(rootctx3, arvados.UserSetupOptions{UUID: user.UUID, VMUUID: outVM.UUID})
641         c.Check(err, check.IsNil)
642
643         var outLinks arvados.LinkList
644         err = rootac3.RequestAndDecode(&outLinks, "GET", "/arvados/v1/links", nil,
645                 arvados.ListOptions{
646                         Limit: 1000,
647                         Filters: []arvados.Filter{
648                                 {
649                                         Attr:     "tail_uuid",
650                                         Operator: "=",
651                                         Operand:  user.UUID,
652                                 },
653                                 {
654                                         Attr:     "head_uuid",
655                                         Operator: "=",
656                                         Operand:  outVM.UUID,
657                                 },
658                                 {
659                                         Attr:     "name",
660                                         Operator: "=",
661                                         Operand:  "can_login",
662                                 },
663                                 {
664                                         Attr:     "link_class",
665                                         Operator: "=",
666                                         Operand:  "permission",
667                                 }}})
668         c.Check(err, check.IsNil)
669
670         c.Check(len(outLinks.Items), check.Equals, 1)
671 }
672
673 func (s *IntegrationSuite) TestOIDCAccessTokenAuth(c *check.C) {
674         conn1 := s.testClusters["z1111"].Conn()
675         rootctx1, _, _ := s.testClusters["z1111"].RootClients()
676         s.testClusters["z1111"].UserClients(rootctx1, c, conn1, s.oidcprovider.AuthEmail, true)
677
678         accesstoken := s.oidcprovider.ValidAccessToken()
679
680         for _, clusterID := range []string{"z1111", "z2222"} {
681
682                 var coll arvados.Collection
683
684                 // Write some file data and create a collection
685                 {
686                         c.Logf("save collection to %s", clusterID)
687
688                         conn := s.testClusters[clusterID].Conn()
689                         ctx, ac, kc := s.testClusters[clusterID].ClientsWithToken(accesstoken)
690
691                         fs, err := coll.FileSystem(ac, kc)
692                         c.Assert(err, check.IsNil)
693                         f, err := fs.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
694                         c.Assert(err, check.IsNil)
695                         _, err = io.WriteString(f, "IntegrationSuite.TestOIDCAccessTokenAuth")
696                         c.Assert(err, check.IsNil)
697                         err = f.Close()
698                         c.Assert(err, check.IsNil)
699                         mtxt, err := fs.MarshalManifest(".")
700                         c.Assert(err, check.IsNil)
701                         coll, err = conn.CollectionCreate(ctx, arvados.CreateOptions{Attrs: map[string]interface{}{
702                                 "manifest_text": mtxt,
703                         }})
704                         c.Assert(err, check.IsNil)
705                 }
706
707                 // Read the collection & file data -- both from the
708                 // cluster where it was created, and from the other
709                 // cluster.
710                 for _, readClusterID := range []string{"z1111", "z2222", "z3333"} {
711                         c.Logf("retrieve %s from %s", coll.UUID, readClusterID)
712
713                         conn := s.testClusters[readClusterID].Conn()
714                         ctx, ac, kc := s.testClusters[readClusterID].ClientsWithToken(accesstoken)
715
716                         user, err := conn.UserGetCurrent(ctx, arvados.GetOptions{})
717                         c.Assert(err, check.IsNil)
718                         c.Check(user.FullName, check.Equals, "Example User")
719                         readcoll, err := conn.CollectionGet(ctx, arvados.GetOptions{UUID: coll.UUID})
720                         c.Assert(err, check.IsNil)
721                         c.Check(readcoll.ManifestText, check.Not(check.Equals), "")
722                         fs, err := readcoll.FileSystem(ac, kc)
723                         c.Assert(err, check.IsNil)
724                         f, err := fs.Open("test.txt")
725                         c.Assert(err, check.IsNil)
726                         buf, err := ioutil.ReadAll(f)
727                         c.Assert(err, check.IsNil)
728                         c.Check(buf, check.DeepEquals, []byte("IntegrationSuite.TestOIDCAccessTokenAuth"))
729                 }
730         }
731 }