5824: Move request auth code into an SDK package. Support more ways of passing tokens.
[arvados.git] / services / arv-git-httpd / server_test.go
1 package main
2
3 import (
4         "errors"
5         "io/ioutil"
6         "os"
7         "os/exec"
8         "strings"
9         "testing"
10
11         "git.curoverse.com/arvados.git/sdk/go/arvadostest"
12         check "gopkg.in/check.v1"
13 )
14
15 var _ = check.Suite(&IntegrationSuite{})
16
17 const (
18         spectatorToken = "zw2f4gwx8hw8cjre7yp6v1zylhrhn3m5gvjq73rtpwhmknrybu"
19         activeToken    = "3kg6k6lzmp9kj5cpkcoxie963cmvjahbt2fod9zru30k1jqdmi"
20         anonymousToken = "4kg6k6lzmp9kj4cpkcoxie964cmvjahbt4fod9zru44k4jqdmi"
21         expiredToken   = "2ym314ysp27sk7h943q6vtc378srb06se3pq6ghurylyf3pdmx"
22 )
23
24 // IntegrationSuite tests need an API server and an arv-git-httpd server
25 type IntegrationSuite struct {
26         tmpRepoRoot string
27         tmpWorkdir  string
28         testServer  *server
29 }
30
31 func (s *IntegrationSuite) TestPathVariants(c *check.C) {
32         s.makeArvadosRepo(c)
33         for _, repo := range []string{"active/foo.git", "active/foo/.git", "arvados.git", "arvados/.git"} {
34                 err := s.runGit(c, spectatorToken, "fetch", repo)
35                 c.Assert(err, check.Equals, nil)
36         }
37 }
38
39 func (s *IntegrationSuite) TestReadonly(c *check.C) {
40         err := s.runGit(c, spectatorToken, "fetch", "active/foo.git")
41         c.Assert(err, check.Equals, nil)
42         err = s.runGit(c, spectatorToken, "push", "active/foo.git", "master:newbranchfail")
43         c.Assert(err, check.ErrorMatches, `.*HTTP code = 403.*`)
44         _, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666/.git/refs/heads/newbranchfail")
45         c.Assert(err, check.FitsTypeOf, &os.PathError{})
46 }
47
48 func (s *IntegrationSuite) TestReadwrite(c *check.C) {
49         err := s.runGit(c, activeToken, "fetch", "active/foo.git")
50         c.Assert(err, check.Equals, nil)
51         err = s.runGit(c, activeToken, "push", "active/foo.git", "master:newbranch")
52         c.Assert(err, check.Equals, nil)
53         _, err = os.Stat(s.tmpRepoRoot + "/zzzzz-s0uqq-382brsig8rp3666/.git/refs/heads/newbranch")
54         c.Assert(err, check.Equals, nil)
55 }
56
57 func (s *IntegrationSuite) TestNonexistent(c *check.C) {
58         err := s.runGit(c, spectatorToken, "fetch", "thisrepodoesnotexist.git")
59         c.Assert(err, check.ErrorMatches, `.* not found.*`)
60 }
61
62 func (s *IntegrationSuite) TestMissingGitdirReadableRepository(c *check.C) {
63         err := s.runGit(c, activeToken, "fetch", "active/foo2.git")
64         c.Assert(err, check.ErrorMatches, `.* not found.*`)
65 }
66
67 func (s *IntegrationSuite) TestNoPermission(c *check.C) {
68         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
69                 err := s.runGit(c, anonymousToken, "fetch", repo)
70                 c.Assert(err, check.ErrorMatches, `.* not found.*`)
71         }
72 }
73
74 func (s *IntegrationSuite) TestExpiredToken(c *check.C) {
75         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
76                 err := s.runGit(c, expiredToken, "fetch", repo)
77                 c.Assert(err, check.ErrorMatches, `.* 500 while accessing.*`)
78         }
79 }
80
81 func (s *IntegrationSuite) TestInvalidToken(c *check.C) {
82         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
83                 err := s.runGit(c, "s3cr3tp@ssw0rd", "fetch", repo)
84                 c.Assert(err, check.ErrorMatches, `.* requested URL returned error.*`)
85         }
86 }
87
88 func (s *IntegrationSuite) TestShortToken(c *check.C) {
89         for _, repo := range []string{"active/foo.git", "active/foo/.git"} {
90                 err := s.runGit(c, "s3cr3t", "fetch", repo)
91                 c.Assert(err, check.ErrorMatches, `.* 500 while accessing.*`)
92         }
93 }
94
95 func (s *IntegrationSuite) TestShortTokenBadReq(c *check.C) {
96         for _, repo := range []string{"bogus"} {
97                 err := s.runGit(c, "s3cr3t", "fetch", repo)
98                 c.Assert(err, check.ErrorMatches, `.* requested URL returned error.*`)
99         }
100 }
101
102 func (s *IntegrationSuite) SetUpSuite(c *check.C) {
103         arvadostest.StartAPI()
104 }
105
106 func (s *IntegrationSuite) SetUpTest(c *check.C) {
107         arvadostest.ResetEnv()
108         s.testServer = &server{}
109         var err error
110         s.tmpRepoRoot, err = ioutil.TempDir("", "arv-git-httpd")
111         c.Assert(err, check.Equals, nil)
112         s.tmpWorkdir, err = ioutil.TempDir("", "arv-git-httpd")
113         c.Assert(err, check.Equals, nil)
114         _, err = exec.Command("git", "init", s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666").Output()
115         c.Assert(err, check.Equals, nil)
116         _, err = exec.Command("sh", "-c", "cd "+s.tmpRepoRoot+"/zzzzz-s0uqq-382brsig8rp3666 && echo test >test && git add test && git -c user.name=Foo -c user.email=Foo commit -am 'foo: test'").CombinedOutput()
117         c.Assert(err, check.Equals, nil)
118         _, err = exec.Command("git", "init", s.tmpWorkdir).Output()
119         c.Assert(err, check.Equals, nil)
120         _, err = exec.Command("sh", "-c", "cd "+s.tmpWorkdir+" && echo work >work && git add work && git -c user.name=Foo -c user.email=Foo commit -am 'workdir: test'").CombinedOutput()
121         c.Assert(err, check.Equals, nil)
122
123         _, err = exec.Command("git", "config",
124                 "--file", s.tmpWorkdir+"/.git/config",
125                 "credential.http://"+s.testServer.Addr+"/.helper",
126                 "!cred(){ cat >/dev/null; if [ \"$1\" = get ]; then echo password=$ARVADOS_API_TOKEN; fi; };cred").Output()
127         c.Assert(err, check.Equals, nil)
128         _, err = exec.Command("git", "config",
129                 "--file", s.tmpWorkdir+"/.git/config",
130                 "credential.http://"+s.testServer.Addr+"/.username",
131                 "none").Output()
132         c.Assert(err, check.Equals, nil)
133
134         theConfig = &config{
135                 Addr:       ":",
136                 GitCommand: "/usr/bin/git",
137                 Root:       s.tmpRepoRoot,
138         }
139         err = s.testServer.Start()
140         c.Assert(err, check.Equals, nil)
141
142         // Clear ARVADOS_API_TOKEN after starting up the server, to
143         // make sure arv-git-httpd doesn't use it.
144         os.Setenv("ARVADOS_API_TOKEN", "unused-token-placates-client-library")
145 }
146
147 func (s *IntegrationSuite) TearDownTest(c *check.C) {
148         var err error
149         if s.testServer != nil {
150                 err = s.testServer.Close()
151         }
152         c.Check(err, check.Equals, nil)
153         if s.tmpRepoRoot != "" {
154                 err = os.RemoveAll(s.tmpRepoRoot)
155                 c.Check(err, check.Equals, nil)
156         }
157         if s.tmpWorkdir != "" {
158                 err = os.RemoveAll(s.tmpWorkdir)
159                 c.Check(err, check.Equals, nil)
160         }
161 }
162
163 func (s *IntegrationSuite) runGit(c *check.C, token, gitCmd, repo string, args ...string) error {
164         cwd, err := os.Getwd()
165         c.Assert(err, check.Equals, nil)
166         defer os.Chdir(cwd)
167         os.Chdir(s.tmpWorkdir)
168
169         gitargs := append([]string{
170                 gitCmd, "http://" + s.testServer.Addr + "/" + repo,
171         }, args...)
172         cmd := exec.Command("git", gitargs...)
173         cmd.Env = append(os.Environ(), "ARVADOS_API_TOKEN="+token)
174         w, err := cmd.StdinPipe()
175         c.Assert(err, check.Equals, nil)
176         w.Close()
177         output, err := cmd.CombinedOutput()
178         c.Log("git ", gitargs, " => ", err)
179         c.Log(string(output))
180         if err != nil && len(output) > 0 {
181                 // If messages appeared on stderr, they are more
182                 // helpful than the err returned by CombinedOutput().
183                 //
184                 // Easier to match error strings without newlines:
185                 err = errors.New(strings.Replace(string(output), "\n", " // ", -1))
186         }
187         return err
188 }
189
190 // Make a bare arvados repo at {tmpRepoRoot}/arvados.git
191 func (s *IntegrationSuite) makeArvadosRepo(c *check.C) {
192         msg, err := exec.Command("git", "init", "--bare", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git").CombinedOutput()
193         c.Log(string(msg))
194         c.Assert(err, check.Equals, nil)
195         msg, err = exec.Command("git", "--git-dir", s.tmpRepoRoot+"/zzzzz-s0uqq-arvadosrepo0123.git", "fetch", "../../.git", "HEAD:master").CombinedOutput()
196         c.Log(string(msg))
197         c.Assert(err, check.Equals, nil)
198 }
199
200 // Gocheck boilerplate
201 func Test(t *testing.T) {
202         check.TestingT(t)
203 }