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