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