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