18947: Refactor arv-git-httpd as an arvados-server subcommand.
[arvados.git] / services / githttpd / auth_handler_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package githttpd
6
7 import (
8         "io"
9         "log"
10         "net/http"
11         "net/http/httptest"
12         "net/url"
13         "path/filepath"
14         "strings"
15
16         "git.arvados.org/arvados.git/lib/config"
17         "git.arvados.org/arvados.git/sdk/go/arvados"
18         "git.arvados.org/arvados.git/sdk/go/arvadosclient"
19         "git.arvados.org/arvados.git/sdk/go/arvadostest"
20         "git.arvados.org/arvados.git/sdk/go/ctxlog"
21         check "gopkg.in/check.v1"
22 )
23
24 var _ = check.Suite(&AuthHandlerSuite{})
25
26 type AuthHandlerSuite struct {
27         cluster *arvados.Cluster
28 }
29
30 func (s *AuthHandlerSuite) SetUpTest(c *check.C) {
31         arvadostest.ResetEnv()
32         repoRoot, err := filepath.Abs("../api/tmp/git/test")
33         c.Assert(err, check.IsNil)
34
35         cfg, err := config.NewLoader(nil, ctxlog.TestLogger(c)).Load()
36         c.Assert(err, check.Equals, nil)
37         s.cluster, err = cfg.GetCluster("")
38         c.Assert(err, check.Equals, nil)
39
40         s.cluster.Services.GitHTTP.InternalURLs = map[arvados.URL]arvados.ServiceInstance{{Host: "localhost:0"}: {}}
41         s.cluster.TLS.Insecure = true
42         s.cluster.Git.GitCommand = "/usr/bin/git"
43         s.cluster.Git.Repositories = repoRoot
44 }
45
46 func (s *AuthHandlerSuite) TestPermission(c *check.C) {
47         client, err := arvados.NewClientFromConfig(s.cluster)
48         c.Assert(err, check.IsNil)
49         ac, err := arvadosclient.New(client)
50         c.Assert(err, check.IsNil)
51         h := &authHandler{
52                 cluster:    s.cluster,
53                 clientPool: &arvadosclient.ClientPool{Prototype: ac},
54                 handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
55                         log.Printf("%v", r.URL)
56                         io.WriteString(w, r.URL.Path)
57                 }),
58         }
59         baseURL, err := url.Parse("http://git.example/")
60         c.Assert(err, check.IsNil)
61         for _, trial := range []struct {
62                 label   string
63                 token   string
64                 pathIn  string
65                 pathOut string
66                 status  int
67         }{
68                 {
69                         label:   "read repo by name",
70                         token:   arvadostest.ActiveToken,
71                         pathIn:  arvadostest.Repository2Name + ".git/git-upload-pack",
72                         pathOut: arvadostest.Repository2UUID + ".git/git-upload-pack",
73                 },
74                 {
75                         label:   "read repo by uuid",
76                         token:   arvadostest.ActiveToken,
77                         pathIn:  arvadostest.Repository2UUID + ".git/git-upload-pack",
78                         pathOut: arvadostest.Repository2UUID + ".git/git-upload-pack",
79                 },
80                 {
81                         label:   "write repo by name",
82                         token:   arvadostest.ActiveToken,
83                         pathIn:  arvadostest.Repository2Name + ".git/git-receive-pack",
84                         pathOut: arvadostest.Repository2UUID + ".git/git-receive-pack",
85                 },
86                 {
87                         label:   "write repo by uuid",
88                         token:   arvadostest.ActiveToken,
89                         pathIn:  arvadostest.Repository2UUID + ".git/git-receive-pack",
90                         pathOut: arvadostest.Repository2UUID + ".git/git-receive-pack",
91                 },
92                 {
93                         label:  "uuid not found",
94                         token:  arvadostest.ActiveToken,
95                         pathIn: strings.Replace(arvadostest.Repository2UUID, "6", "z", -1) + ".git/git-upload-pack",
96                         status: http.StatusNotFound,
97                 },
98                 {
99                         label:  "name not found",
100                         token:  arvadostest.ActiveToken,
101                         pathIn: "nonexistent-bogus.git/git-upload-pack",
102                         status: http.StatusNotFound,
103                 },
104                 {
105                         label:   "read read-only repo",
106                         token:   arvadostest.SpectatorToken,
107                         pathIn:  arvadostest.FooRepoName + ".git/git-upload-pack",
108                         pathOut: arvadostest.FooRepoUUID + "/.git/git-upload-pack",
109                 },
110                 {
111                         label:  "write read-only repo",
112                         token:  arvadostest.SpectatorToken,
113                         pathIn: arvadostest.FooRepoName + ".git/git-receive-pack",
114                         status: http.StatusForbidden,
115                 },
116         } {
117                 c.Logf("trial label: %q", trial.label)
118                 u, err := baseURL.Parse(trial.pathIn)
119                 c.Assert(err, check.IsNil)
120                 resp := httptest.NewRecorder()
121                 req := &http.Request{
122                         Method: "POST",
123                         URL:    u,
124                         Header: http.Header{
125                                 "Authorization": {"Bearer " + trial.token}}}
126                 h.ServeHTTP(resp, req)
127                 if trial.status == 0 {
128                         trial.status = http.StatusOK
129                 }
130                 c.Check(resp.Code, check.Equals, trial.status)
131                 if trial.status < 400 {
132                         if trial.pathOut != "" && !strings.HasPrefix(trial.pathOut, "/") {
133                                 trial.pathOut = "/" + trial.pathOut
134                         }
135                         c.Check(resp.Body.String(), check.Equals, trial.pathOut)
136                 }
137         }
138 }
139
140 func (s *AuthHandlerSuite) TestCORS(c *check.C) {
141         h := &authHandler{cluster: s.cluster}
142
143         // CORS preflight
144         resp := httptest.NewRecorder()
145         req := &http.Request{
146                 Method: "OPTIONS",
147                 Header: http.Header{
148                         "Origin":                        {"*"},
149                         "Access-Control-Request-Method": {"GET"},
150                 },
151         }
152         h.ServeHTTP(resp, req)
153         c.Check(resp.Code, check.Equals, http.StatusOK)
154         c.Check(resp.Header().Get("Access-Control-Allow-Methods"), check.Equals, "GET, POST")
155         c.Check(resp.Header().Get("Access-Control-Allow-Headers"), check.Equals, "Authorization, Content-Type")
156         c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
157         c.Check(resp.Body.String(), check.Equals, "")
158
159         // CORS actual request. Bogus token and path ensure
160         // authHandler responds 4xx without calling our wrapped (nil)
161         // handler.
162         u, err := url.Parse("git.zzzzz.arvadosapi.com/test")
163         c.Assert(err, check.Equals, nil)
164         resp = httptest.NewRecorder()
165         req = &http.Request{
166                 Method: "GET",
167                 URL:    u,
168                 Header: http.Header{
169                         "Origin":        {"*"},
170                         "Authorization": {"OAuth2 foobar"},
171                 },
172         }
173         h.ServeHTTP(resp, req)
174         c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
175 }