X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/e67d0f5d43c56f78694ea4a5f93acec5c93cd0fb..0a9d4fa5043bd7291611c41588bdd3f0b70ede44:/services/keep-web/cadaver_test.go diff --git a/services/keep-web/cadaver_test.go b/services/keep-web/cadaver_test.go index 87a712f04c..e965a6e8c2 100644 --- a/services/keep-web/cadaver_test.go +++ b/services/keep-web/cadaver_test.go @@ -6,55 +6,369 @@ package main import ( "bytes" + "fmt" "io" + "io/ioutil" + "os" "os/exec" + "path/filepath" + "strings" + "time" - "git.curoverse.com/arvados.git/sdk/go/arvadostest" + "git.arvados.org/arvados.git/sdk/go/arvados" + "git.arvados.org/arvados.git/sdk/go/arvadostest" check "gopkg.in/check.v1" ) -func (s *IntegrationSuite) TestWebdavWithCadaver(c *check.C) { - basePath := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/" +func (s *IntegrationSuite) TestCadaverHTTPAuth(c *check.C) { + s.testCadaver(c, arvadostest.ActiveToken, func(newCollection arvados.Collection) (string, string, string) { + r := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/" + w := "/c=" + newCollection.UUID + "/" + pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/" + return r, w, pdh + }, nil) +} + +func (s *IntegrationSuite) TestCadaverPathAuth(c *check.C) { + s.testCadaver(c, "", func(newCollection arvados.Collection) (string, string, string) { + r := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/" + w := "/c=" + newCollection.UUID + "/t=" + arvadostest.ActiveToken + "/" + pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/t=" + arvadostest.ActiveToken + "/" + return r, w, pdh + }, nil) +} + +func (s *IntegrationSuite) TestCadaverUserProject(c *check.C) { + rpath := "/users/active/foo_file_in_dir/" + s.testCadaver(c, arvadostest.ActiveToken, func(newCollection arvados.Collection) (string, string, string) { + wpath := "/users/active/" + newCollection.Name + pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/" + return rpath, wpath, pdh + }, func(path string) bool { + // Skip tests that rely on writes, because /users/ + // tree is read-only. + return !strings.HasPrefix(path, rpath) || strings.HasPrefix(path, rpath+"_/") + }) +} + +func (s *IntegrationSuite) testCadaver(c *check.C, password string, pathFunc func(arvados.Collection) (string, string, string), skip func(string) bool) { + s.testServer.Config.cluster.Users.AnonymousUserToken = arvadostest.AnonymousToken + + testdata := []byte("the human tragedy consists in the necessity of living with the consequences of actions performed under the pressure of compulsions we do not understand") + + tempdir, err := ioutil.TempDir("", "keep-web-test-") + c.Assert(err, check.IsNil) + defer os.RemoveAll(tempdir) + + localfile, err := ioutil.TempFile(tempdir, "localfile") + c.Assert(err, check.IsNil) + localfile.Write(testdata) + + emptyfile, err := ioutil.TempFile(tempdir, "emptyfile") + c.Assert(err, check.IsNil) + + checkfile, err := ioutil.TempFile(tempdir, "checkfile") + c.Assert(err, check.IsNil) + + var newCollection arvados.Collection + arv := arvados.NewClientFromEnv() + arv.AuthToken = arvadostest.ActiveToken + err = arv.RequestAndDecode(&newCollection, "POST", "arvados/v1/collections", nil, map[string]interface{}{"collection": map[string]interface{}{}}) + c.Assert(err, check.IsNil) + + readPath, writePath, pdhPath := pathFunc(newCollection) + + matchToday := time.Now().Format("Jan +2") + type testcase struct { path string cmd string match string + data []byte } for _, trial := range []testcase{ { - path: basePath, + path: readPath, cmd: "ls\n", match: `(?ms).*dir1 *0 .*`, }, { - path: basePath, + path: readPath, cmd: "ls dir1\n", match: `(?ms).*bar *3.*foo *3 .*`, }, { - path: basePath + "_/dir1", + path: readPath + "_/dir1", cmd: "ls\n", match: `(?ms).*bar *3.*foo *3 .*`, }, { - path: basePath + "dir1/", + path: readPath + "dir1/", cmd: "ls\n", - match: `(?ms).*bar *3.*foo *3 .*`, + match: `(?ms).*bar *3.*foo +3 +Feb +\d+ +2014.*`, + }, + { + path: writePath, + cmd: "get emptyfile '" + checkfile.Name() + "'\n", + match: `(?ms).*Not Found.*`, + }, + { + path: writePath, + cmd: "put '" + emptyfile.Name() + "' emptyfile\n", + match: `(?ms).*Uploading .* succeeded.*`, + }, + { + path: writePath, + cmd: "get emptyfile '" + checkfile.Name() + "'\n", + match: `(?ms).*Downloading .* succeeded.*`, + data: []byte{}, + }, + { + path: writePath, + cmd: "put '" + localfile.Name() + "' testfile\n", + match: `(?ms).*Uploading .* succeeded.*`, + }, + { + path: writePath, + cmd: "get testfile '" + checkfile.Name() + "'\n", + match: `(?ms).*succeeded.*`, + data: testdata, }, + { + path: writePath, + cmd: "move testfile \"test &#!%20 file\"\n", + match: `(?ms).*Moving .* succeeded.*`, + }, + { + path: writePath, + cmd: "move \"test &#!%20 file\" testfile\n", + match: `(?ms).*Moving .* succeeded.*`, + }, + { + path: writePath, + cmd: "move testfile newdir0/\n", + match: `(?ms).*Moving .* succeeded.*`, + }, + { + path: writePath, + cmd: "move testfile newdir0/\n", + match: `(?ms).*Moving .* failed.*`, + }, + { + path: writePath, + cmd: "lock newdir0/testfile\n", + match: `(?ms).*Locking .* succeeded.*`, + }, + { + path: writePath, + cmd: "unlock newdir0/testfile\nasdf\n", + match: `(?ms).*Unlocking .* succeeded.*`, + }, + { + path: writePath, + cmd: "ls\n", + match: `(?ms).*newdir0.* 0 +` + matchToday + ` \d+:\d+\n.*`, + }, + { + path: writePath, + cmd: "move newdir0/testfile emptyfile/bogus/\n", + match: `(?ms).*Moving .* failed.*`, + }, + { + path: writePath, + cmd: "mkcol newdir1\n", + match: `(?ms).*Creating .* succeeded.*`, + }, + { + path: writePath, + cmd: "move newdir1/ newdir1x/\n", + match: `(?ms).*Moving .* succeeded.*`, + }, + { + path: writePath, + cmd: "move newdir1x newdir1\n", + match: `(?ms).*Moving .* succeeded.*`, + }, + { + path: writePath, + cmd: "move newdir0/testfile newdir1/\n", + match: `(?ms).*Moving .* succeeded.*`, + }, + { + path: writePath, + cmd: "move newdir1 newdir1/\n", + match: `(?ms).*Moving .* failed.*`, + }, + { + path: writePath, + cmd: "get newdir1/testfile '" + checkfile.Name() + "'\n", + match: `(?ms).*succeeded.*`, + data: testdata, + }, + { + path: writePath, + cmd: "put '" + localfile.Name() + "' newdir1/testfile1\n", + match: `(?ms).*Uploading .* succeeded.*`, + }, + { + path: writePath, + cmd: "mkcol newdir2\n", + match: `(?ms).*Creating .* succeeded.*`, + }, + { + path: writePath, + cmd: "put '" + localfile.Name() + "' newdir2/testfile2\n", + match: `(?ms).*Uploading .* succeeded.*`, + }, + { + path: writePath, + cmd: "copy newdir2/testfile2 testfile3\n", + match: `(?ms).*succeeded.*`, + }, + { + path: writePath, + cmd: "get testfile3 '" + checkfile.Name() + "'\n", + match: `(?ms).*succeeded.*`, + data: testdata, + }, + { + path: writePath, + cmd: "get newdir2/testfile2 '" + checkfile.Name() + "'\n", + match: `(?ms).*succeeded.*`, + data: testdata, + }, + { + path: writePath, + cmd: "rmcol newdir2\n", + match: `(?ms).*Deleting collection .* succeeded.*`, + }, + { + path: writePath, + cmd: "get newdir2/testfile2 '" + checkfile.Name() + "'\n", + match: `(?ms).*Downloading .* failed.*`, + }, + { + path: "/c=" + arvadostest.UserAgreementCollection + "/t=" + arv.AuthToken + "/", + cmd: "put '" + localfile.Name() + "' foo\n", + match: `(?ms).*Uploading .* failed:.*403 Forbidden.*`, + }, + { + path: pdhPath, + cmd: "put '" + localfile.Name() + "' foo\n", + match: `(?ms).*Uploading .* failed:.*405 Method Not Allowed.*`, + }, + { + path: pdhPath, + cmd: "move foo bar\n", + match: `(?ms).*Moving .* failed:.*405 Method Not Allowed.*`, + }, + { + path: pdhPath, + cmd: "copy foo bar\n", + match: `(?ms).*Copying .* failed:.*405 Method Not Allowed.*`, + }, + { + path: pdhPath, + cmd: "delete foo\n", + match: `(?ms).*Deleting .* failed:.*405 Method Not Allowed.*`, + }, + { + path: pdhPath, + cmd: "lock foo\n", + match: `(?ms).*Locking .* failed:.*405 Method Not Allowed.*`, + }, + } { + c.Logf("%s %+v", "http://"+s.testServer.Addr, trial) + if skip != nil && skip(trial.path) { + c.Log("(skip)") + continue + } + + os.Remove(checkfile.Name()) + + stdout := s.runCadaver(c, password, trial.path, trial.cmd) + c.Check(stdout, check.Matches, trial.match) + + if trial.data == nil { + continue + } + checkfile, err = os.Open(checkfile.Name()) + c.Assert(err, check.IsNil) + checkfile.Seek(0, os.SEEK_SET) + got, err := ioutil.ReadAll(checkfile) + c.Check(got, check.DeepEquals, trial.data) + c.Check(err, check.IsNil) + } +} + +func (s *IntegrationSuite) TestCadaverByID(c *check.C) { + for _, path := range []string{"/by_id", "/by_id/"} { + stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls") + c.Check(stdout, check.Matches, `(?ms).*collection is empty.*`) + } + for _, path := range []string{ + "/by_id/" + arvadostest.FooCollectionPDH, + "/by_id/" + arvadostest.FooCollectionPDH + "/", + "/by_id/" + arvadostest.FooCollection, + "/by_id/" + arvadostest.FooCollection + "/", } { - c.Logf("%s %#v", "http://"+s.testServer.Addr, trial) - cmd := exec.Command("cadaver", "http://"+s.testServer.Addr+trial.path) - cmd.Stdin = bytes.NewBufferString(trial.cmd) - stdout, err := cmd.StdoutPipe() - c.Assert(err, check.Equals, nil) - cmd.Stderr = cmd.Stdout - go cmd.Start() - - var buf bytes.Buffer - _, err = io.Copy(&buf, stdout) - c.Check(err, check.Equals, nil) - err = cmd.Wait() - c.Check(err, check.Equals, nil) - c.Check(buf.String(), check.Matches, trial.match) + stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls") + c.Check(stdout, check.Matches, `(?ms).*\s+foo\s+3 .*`) } } + +func (s *IntegrationSuite) TestCadaverUsersDir(c *check.C) { + for _, path := range []string{"/"} { + stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls") + c.Check(stdout, check.Matches, `(?ms).*Coll:\s+by_id\s+0 .*`) + c.Check(stdout, check.Matches, `(?ms).*Coll:\s+users\s+0 .*`) + } + for _, path := range []string{"/users", "/users/"} { + stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls") + c.Check(stdout, check.Matches, `(?ms).*Coll:\s+active.*`) + } + for _, path := range []string{"/users/active", "/users/active/"} { + stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls") + c.Check(stdout, check.Matches, `(?ms).*Coll:\s+A Project\s+0 .*`) + c.Check(stdout, check.Matches, `(?ms).*Coll:\s+bar_file\s+0 .*`) + } + for _, path := range []string{"/users/admin", "/users/doesnotexist", "/users/doesnotexist/"} { + stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls") + c.Check(stdout, check.Matches, `(?ms).*404 Not Found.*`) + } +} + +func (s *IntegrationSuite) runCadaver(c *check.C, password, path, stdin string) string { + tempdir, err := ioutil.TempDir("", "keep-web-test-") + c.Assert(err, check.IsNil) + defer os.RemoveAll(tempdir) + + cmd := exec.Command("cadaver", "http://"+s.testServer.Addr+path) + if password != "" { + // cadaver won't try username/password authentication + // unless the server responds 401 to an + // unauthenticated request, which it only does in + // AttachmentOnlyHost, TrustAllContent, and + // per-collection vhost cases. + s.testServer.Config.cluster.Services.WebDAVDownload.ExternalURL.Host = s.testServer.Addr + + cmd.Env = append(os.Environ(), "HOME="+tempdir) + f, err := os.OpenFile(filepath.Join(tempdir, ".netrc"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600) + c.Assert(err, check.IsNil) + _, err = fmt.Fprintf(f, "default login none password %s\n", password) + c.Assert(err, check.IsNil) + c.Assert(f.Close(), check.IsNil) + } + cmd.Stdin = bytes.NewBufferString(stdin) + stdout, err := cmd.StdoutPipe() + c.Assert(err, check.Equals, nil) + cmd.Stderr = cmd.Stdout + go cmd.Start() + + var buf bytes.Buffer + _, err = io.Copy(&buf, stdout) + c.Check(err, check.Equals, nil) + err = cmd.Wait() + c.Check(err, check.Equals, nil) + return buf.String() +}