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