13111: Merge branch 'master' into 13111-webdav-projects
[arvados.git] / services / keep-web / cadaver_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package main
6
7 import (
8         "bytes"
9         "fmt"
10         "io"
11         "io/ioutil"
12         "net/url"
13         "os"
14         "os/exec"
15         "path/filepath"
16         "strings"
17         "time"
18
19         "git.curoverse.com/arvados.git/sdk/go/arvados"
20         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
21         check "gopkg.in/check.v1"
22 )
23
24 func (s *IntegrationSuite) TestCadaverHTTPAuth(c *check.C) {
25         s.testCadaver(c, arvadostest.ActiveToken, func(newCollection arvados.Collection) (string, string, string) {
26                 r := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/"
27                 w := "/c=" + newCollection.UUID + "/"
28                 pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/"
29                 return r, w, pdh
30         }, nil)
31 }
32
33 func (s *IntegrationSuite) TestCadaverPathAuth(c *check.C) {
34         s.testCadaver(c, "", func(newCollection arvados.Collection) (string, string, string) {
35                 r := "/c=" + arvadostest.FooAndBarFilesInDirUUID + "/t=" + arvadostest.ActiveToken + "/"
36                 w := "/c=" + newCollection.UUID + "/t=" + arvadostest.ActiveToken + "/"
37                 pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/t=" + arvadostest.ActiveToken + "/"
38                 return r, w, pdh
39         }, nil)
40 }
41
42 func (s *IntegrationSuite) TestCadaverUserProject(c *check.C) {
43         rpath := "/users/active/foo_file_in_dir/"
44         s.testCadaver(c, arvadostest.ActiveToken, func(newCollection arvados.Collection) (string, string, string) {
45                 wpath := "/users/active/" + newCollection.Name
46                 pdh := "/c=" + strings.Replace(arvadostest.FooAndBarFilesInDirPDH, "+", "-", -1) + "/"
47                 return rpath, wpath, pdh
48         }, func(path string) bool {
49                 // Skip tests that rely on writes, because /users/
50                 // tree is read-only.
51                 return !strings.HasPrefix(path, rpath) || strings.HasPrefix(path, rpath+"_/")
52         })
53 }
54
55 func (s *IntegrationSuite) testCadaver(c *check.C, password string, pathFunc func(arvados.Collection) (string, string, string), skip func(string) bool) {
56         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")
57
58         tempdir, err := ioutil.TempDir("", "keep-web-test-")
59         c.Assert(err, check.IsNil)
60         defer os.RemoveAll(tempdir)
61
62         localfile, err := ioutil.TempFile(tempdir, "localfile")
63         c.Assert(err, check.IsNil)
64         localfile.Write(testdata)
65
66         emptyfile, err := ioutil.TempFile(tempdir, "emptyfile")
67         c.Assert(err, check.IsNil)
68
69         checkfile, err := ioutil.TempFile(tempdir, "checkfile")
70         c.Assert(err, check.IsNil)
71
72         var newCollection arvados.Collection
73         arv := arvados.NewClientFromEnv()
74         arv.AuthToken = arvadostest.ActiveToken
75         err = arv.RequestAndDecode(&newCollection, "POST", "/arvados/v1/collections", bytes.NewBufferString(url.Values{"collection": {"{}"}}.Encode()), nil)
76         c.Assert(err, check.IsNil)
77
78         readPath, writePath, pdhPath := pathFunc(newCollection)
79
80         matchToday := time.Now().Format("Jan +2")
81
82         type testcase struct {
83                 path  string
84                 cmd   string
85                 match string
86                 data  []byte
87         }
88         for _, trial := range []testcase{
89                 {
90                         path:  readPath,
91                         cmd:   "ls\n",
92                         match: `(?ms).*dir1 *0 .*`,
93                 },
94                 {
95                         path:  readPath,
96                         cmd:   "ls dir1\n",
97                         match: `(?ms).*bar *3.*foo *3 .*`,
98                 },
99                 {
100                         path:  readPath + "_/dir1",
101                         cmd:   "ls\n",
102                         match: `(?ms).*bar *3.*foo *3 .*`,
103                 },
104                 {
105                         path:  readPath + "dir1/",
106                         cmd:   "ls\n",
107                         match: `(?ms).*bar *3.*foo +3 +Feb +\d+ +2014.*`,
108                 },
109                 {
110                         path:  writePath,
111                         cmd:   "get emptyfile '" + checkfile.Name() + "'\n",
112                         match: `(?ms).*Not Found.*`,
113                 },
114                 {
115                         path:  writePath,
116                         cmd:   "put '" + emptyfile.Name() + "' emptyfile\n",
117                         match: `(?ms).*Uploading .* succeeded.*`,
118                 },
119                 {
120                         path:  writePath,
121                         cmd:   "get emptyfile '" + checkfile.Name() + "'\n",
122                         match: `(?ms).*Downloading .* succeeded.*`,
123                         data:  []byte{},
124                 },
125                 {
126                         path:  writePath,
127                         cmd:   "put '" + localfile.Name() + "' testfile\n",
128                         match: `(?ms).*Uploading .* succeeded.*`,
129                 },
130                 {
131                         path:  writePath,
132                         cmd:   "get testfile '" + checkfile.Name() + "'\n",
133                         match: `(?ms).*succeeded.*`,
134                         data:  testdata,
135                 },
136                 {
137                         path:  writePath,
138                         cmd:   "move testfile newdir0/\n",
139                         match: `(?ms).*Moving .* succeeded.*`,
140                 },
141                 {
142                         path:  writePath,
143                         cmd:   "move testfile newdir0/\n",
144                         match: `(?ms).*Moving .* failed.*`,
145                 },
146                 {
147                         path:  writePath,
148                         cmd:   "ls\n",
149                         match: `(?ms).*newdir0.* 0 +` + matchToday + ` \d+:\d+\n.*`,
150                 },
151                 {
152                         path:  writePath,
153                         cmd:   "move newdir0/testfile emptyfile/bogus/\n",
154                         match: `(?ms).*Moving .* failed.*`,
155                 },
156                 {
157                         path:  writePath,
158                         cmd:   "mkcol newdir1\n",
159                         match: `(?ms).*Creating .* succeeded.*`,
160                 },
161                 {
162                         path:  writePath,
163                         cmd:   "move newdir0/testfile newdir1/\n",
164                         match: `(?ms).*Moving .* succeeded.*`,
165                 },
166                 {
167                         path:  writePath,
168                         cmd:   "move newdir1 newdir1/\n",
169                         match: `(?ms).*Moving .* failed.*`,
170                 },
171                 {
172                         path:  writePath,
173                         cmd:   "get newdir1/testfile '" + checkfile.Name() + "'\n",
174                         match: `(?ms).*succeeded.*`,
175                         data:  testdata,
176                 },
177                 {
178                         path:  writePath,
179                         cmd:   "put '" + localfile.Name() + "' newdir1/testfile1\n",
180                         match: `(?ms).*Uploading .* succeeded.*`,
181                 },
182                 {
183                         path:  writePath,
184                         cmd:   "mkcol newdir2\n",
185                         match: `(?ms).*Creating .* succeeded.*`,
186                 },
187                 {
188                         path:  writePath,
189                         cmd:   "put '" + localfile.Name() + "' newdir2/testfile2\n",
190                         match: `(?ms).*Uploading .* succeeded.*`,
191                 },
192                 {
193                         path:  writePath,
194                         cmd:   "copy newdir2/testfile2 testfile3\n",
195                         match: `(?ms).*succeeded.*`,
196                 },
197                 {
198                         path:  writePath,
199                         cmd:   "get testfile3 '" + checkfile.Name() + "'\n",
200                         match: `(?ms).*succeeded.*`,
201                         data:  testdata,
202                 },
203                 {
204                         path:  writePath,
205                         cmd:   "get newdir2/testfile2 '" + checkfile.Name() + "'\n",
206                         match: `(?ms).*succeeded.*`,
207                         data:  testdata,
208                 },
209                 {
210                         path:  writePath,
211                         cmd:   "rmcol newdir2\n",
212                         match: `(?ms).*Deleting collection .* succeeded.*`,
213                 },
214                 {
215                         path:  writePath,
216                         cmd:   "get newdir2/testfile2 '" + checkfile.Name() + "'\n",
217                         match: `(?ms).*Downloading .* failed.*`,
218                 },
219                 {
220                         path:  "/c=" + arvadostest.UserAgreementCollection + "/t=" + arv.AuthToken + "/",
221                         cmd:   "put '" + localfile.Name() + "' foo\n",
222                         match: `(?ms).*Uploading .* failed:.*403 Forbidden.*`,
223                 },
224                 {
225                         path:  pdhPath,
226                         cmd:   "put '" + localfile.Name() + "' foo\n",
227                         match: `(?ms).*Uploading .* failed:.*405 Method Not Allowed.*`,
228                 },
229                 {
230                         path:  pdhPath,
231                         cmd:   "move foo bar\n",
232                         match: `(?ms).*Moving .* failed:.*405 Method Not Allowed.*`,
233                 },
234                 {
235                         path:  pdhPath,
236                         cmd:   "copy foo bar\n",
237                         match: `(?ms).*Copying .* failed:.*405 Method Not Allowed.*`,
238                 },
239                 {
240                         path:  pdhPath,
241                         cmd:   "delete foo\n",
242                         match: `(?ms).*Deleting .* failed:.*405 Method Not Allowed.*`,
243                 },
244         } {
245                 c.Logf("%s %+v", "http://"+s.testServer.Addr, trial)
246                 if skip != nil && skip(trial.path) {
247                         c.Log("(skip)")
248                         continue
249                 }
250
251                 os.Remove(checkfile.Name())
252
253                 stdout := s.runCadaver(c, password, trial.path, trial.cmd)
254                 c.Check(stdout, check.Matches, trial.match)
255
256                 if trial.data == nil {
257                         continue
258                 }
259                 checkfile, err = os.Open(checkfile.Name())
260                 c.Assert(err, check.IsNil)
261                 checkfile.Seek(0, os.SEEK_SET)
262                 got, err := ioutil.ReadAll(checkfile)
263                 c.Check(got, check.DeepEquals, trial.data)
264                 c.Check(err, check.IsNil)
265         }
266 }
267
268 func (s *IntegrationSuite) TestCadaverByID(c *check.C) {
269         for _, path := range []string{"/by_id", "/by_id/"} {
270                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
271                 c.Check(stdout, check.Matches, `(?ms).*collection is empty.*`)
272         }
273         for _, path := range []string{
274                 "/by_id/" + arvadostest.FooPdh,
275                 "/by_id/" + arvadostest.FooPdh + "/",
276                 "/by_id/" + arvadostest.FooCollection,
277                 "/by_id/" + arvadostest.FooCollection + "/",
278         } {
279                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
280                 c.Check(stdout, check.Matches, `(?ms).*\s+foo\s+3 .*`)
281         }
282 }
283
284 func (s *IntegrationSuite) TestCadaverUsersDir(c *check.C) {
285         for _, path := range []string{"/"} {
286                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
287                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+by_id\s+0 .*`)
288                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+users\s+0 .*`)
289         }
290         for _, path := range []string{"/users", "/users/"} {
291                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
292                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+active.*`)
293         }
294         for _, path := range []string{"/users/active", "/users/active/"} {
295                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
296                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+A Project\s+0 .*`)
297                 c.Check(stdout, check.Matches, `(?ms).*Coll:\s+bar_file\s+0 .*`)
298         }
299         for _, path := range []string{"/users/admin", "/users/doesnotexist", "/users/doesnotexist/"} {
300                 stdout := s.runCadaver(c, arvadostest.ActiveToken, path, "ls")
301                 c.Check(stdout, check.Matches, `(?ms).*404 Not Found.*`)
302         }
303 }
304
305 func (s *IntegrationSuite) runCadaver(c *check.C, password, path, stdin string) string {
306         tempdir, err := ioutil.TempDir("", "keep-web-test-")
307         c.Assert(err, check.IsNil)
308         defer os.RemoveAll(tempdir)
309
310         cmd := exec.Command("cadaver", "http://"+s.testServer.Addr+path)
311         if password != "" {
312                 // cadaver won't try username/password authentication
313                 // unless the server responds 401 to an
314                 // unauthenticated request, which it only does in
315                 // AttachmentOnlyHost, TrustAllContent, and
316                 // per-collection vhost cases.
317                 s.testServer.Config.AttachmentOnlyHost = s.testServer.Addr
318
319                 cmd.Env = append(os.Environ(), "HOME="+tempdir)
320                 f, err := os.OpenFile(filepath.Join(tempdir, ".netrc"), os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0600)
321                 c.Assert(err, check.IsNil)
322                 _, err = fmt.Fprintf(f, "default login none password %s\n", password)
323                 c.Assert(err, check.IsNil)
324                 c.Assert(f.Close(), check.IsNil)
325         }
326         cmd.Stdin = bytes.NewBufferString(stdin)
327         stdout, err := cmd.StdoutPipe()
328         c.Assert(err, check.Equals, nil)
329         cmd.Stderr = cmd.Stdout
330         go cmd.Start()
331
332         var buf bytes.Buffer
333         _, err = io.Copy(&buf, stdout)
334         c.Check(err, check.Equals, nil)
335         err = cmd.Wait()
336         c.Check(err, check.Equals, nil)
337         return buf.String()
338 }