12 "git.curoverse.com/arvados.git/sdk/go/arvadostest"
13 "git.curoverse.com/arvados.git/sdk/go/auth"
14 check "gopkg.in/check.v1"
17 var _ = check.Suite(&UnitSuite{})
19 type UnitSuite struct{}
21 func mustParseURL(s string) *url.URL {
22 r, err := url.Parse(s)
24 panic("parse URL: " + s)
29 func (s *IntegrationSuite) TestVhost404(c *check.C) {
30 for _, testURL := range []string{
31 arvadostest.NonexistentCollection + ".example.com/theperthcountyconspiracy",
32 arvadostest.NonexistentCollection + ".example.com/t=" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
34 resp := httptest.NewRecorder()
37 URL: mustParseURL(testURL),
39 (&handler{}).ServeHTTP(resp, req)
40 c.Check(resp.Code, check.Equals, http.StatusNotFound)
41 c.Check(resp.Body.String(), check.Equals, "")
45 // An authorizer modifies an HTTP request to make use of the given
46 // token -- by adding it to a header, cookie, query param, or whatever
47 // -- and returns the HTTP status code we should expect from keep-web if
48 // the token is invalid.
49 type authorizer func(*http.Request, string) int
51 func (s *IntegrationSuite) TestVhostViaAuthzHeader(c *check.C) {
52 doVhostRequests(c, authzViaAuthzHeader)
54 func authzViaAuthzHeader(r *http.Request, tok string) int {
55 r.Header.Add("Authorization", "OAuth2 "+tok)
56 return http.StatusUnauthorized
59 func (s *IntegrationSuite) TestVhostViaCookieValue(c *check.C) {
60 doVhostRequests(c, authzViaCookieValue)
62 func authzViaCookieValue(r *http.Request, tok string) int {
63 r.AddCookie(&http.Cookie{
64 Name: "arvados_api_token",
65 Value: auth.EncodeTokenCookie([]byte(tok)),
67 return http.StatusUnauthorized
70 func (s *IntegrationSuite) TestVhostViaPath(c *check.C) {
71 doVhostRequests(c, authzViaPath)
73 func authzViaPath(r *http.Request, tok string) int {
74 r.URL.Path = "/t=" + tok + r.URL.Path
75 return http.StatusNotFound
78 func (s *IntegrationSuite) TestVhostViaQueryString(c *check.C) {
79 doVhostRequests(c, authzViaQueryString)
81 func authzViaQueryString(r *http.Request, tok string) int {
82 r.URL.RawQuery = "api_token=" + tok
83 return http.StatusUnauthorized
86 func (s *IntegrationSuite) TestVhostViaPOST(c *check.C) {
87 doVhostRequests(c, authzViaPOST)
89 func authzViaPOST(r *http.Request, tok string) int {
91 r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
92 r.Body = ioutil.NopCloser(strings.NewReader(
93 url.Values{"api_token": {tok}}.Encode()))
94 return http.StatusUnauthorized
97 // Try some combinations of {url, token} using the given authorization
98 // mechanism, and verify the result is correct.
99 func doVhostRequests(c *check.C, authz authorizer) {
100 for _, hostPath := range []string{
101 arvadostest.FooCollection + ".example.com/foo",
102 arvadostest.FooCollection + "--collections.example.com/foo",
103 arvadostest.FooCollection + "--collections.example.com/_/foo",
104 arvadostest.FooPdh + ".example.com/foo",
105 strings.Replace(arvadostest.FooPdh, "+", "-", -1) + "--collections.example.com/foo",
106 arvadostest.FooBarDirCollection + ".example.com/dir1/foo",
108 c.Log("doRequests: ", hostPath)
109 doVhostRequestsWithHostPath(c, authz, hostPath)
113 func doVhostRequestsWithHostPath(c *check.C, authz authorizer, hostPath string) {
114 for _, tok := range []string{
115 arvadostest.ActiveToken,
116 arvadostest.ActiveToken[:15],
117 arvadostest.SpectatorToken,
121 u := mustParseURL("http://" + hostPath)
122 req := &http.Request{
126 Header: http.Header{},
128 failCode := authz(req, tok)
130 code, body := resp.Code, resp.Body.String()
131 if tok == arvadostest.ActiveToken {
132 c.Check(code, check.Equals, http.StatusOK)
133 c.Check(body, check.Equals, "foo")
135 c.Check(code >= 400, check.Equals, true)
136 c.Check(code < 500, check.Equals, true)
137 if tok == arvadostest.SpectatorToken {
138 // Valid token never offers to retry
139 // with different credentials.
140 c.Check(code, check.Equals, http.StatusNotFound)
142 // Invalid token can ask to retry
143 // depending on the authz method.
144 c.Check(code, check.Equals, failCode)
146 c.Check(body, check.Equals, "")
151 func doReq(req *http.Request) *httptest.ResponseRecorder {
152 resp := httptest.NewRecorder()
153 (&handler{}).ServeHTTP(resp, req)
154 if resp.Code != http.StatusSeeOther {
157 cookies := (&http.Response{Header: resp.Header()}).Cookies()
158 u, _ := req.URL.Parse(resp.Header().Get("Location"))
163 Header: http.Header{},
165 for _, c := range cookies {
171 func (s *IntegrationSuite) TestVhostRedirectQueryTokenToCookie(c *check.C) {
172 s.testVhostRedirectTokenToCookie(c, "GET",
173 arvadostest.FooCollection+".example.com/foo",
174 "?api_token="+arvadostest.ActiveToken,
182 func (s *IntegrationSuite) TestSingleOriginSecretLink(c *check.C) {
183 s.testVhostRedirectTokenToCookie(c, "GET",
184 "example.com/c="+arvadostest.FooCollection+"/t="+arvadostest.ActiveToken+"/foo",
193 // Bad token in URL is 404 Not Found because it doesn't make sense to
194 // retry the same URL with different authorization.
195 func (s *IntegrationSuite) TestSingleOriginSecretLinkBadToken(c *check.C) {
196 s.testVhostRedirectTokenToCookie(c, "GET",
197 "example.com/c="+arvadostest.FooCollection+"/t=bogus/foo",
206 // Bad token in a cookie (even if it got there via our own
207 // query-string-to-cookie redirect) is, in principle, retryable at the
208 // same URL so it's 401 Unauthorized.
209 func (s *IntegrationSuite) TestVhostRedirectQueryTokenToBogusCookie(c *check.C) {
210 s.testVhostRedirectTokenToCookie(c, "GET",
211 arvadostest.FooCollection+".example.com/foo",
212 "?api_token=thisisabogustoken",
215 http.StatusUnauthorized,
220 func (s *IntegrationSuite) TestVhostRedirectQueryTokenSingleOriginError(c *check.C) {
221 s.testVhostRedirectTokenToCookie(c, "GET",
222 "example.com/c="+arvadostest.FooCollection+"/foo",
223 "?api_token="+arvadostest.ActiveToken,
226 http.StatusBadRequest,
231 func (s *IntegrationSuite) TestVhostRedirectQueryTokenTrustAllContent(c *check.C) {
232 defer func(orig bool) {
233 trustAllContent = orig
235 trustAllContent = true
236 s.testVhostRedirectTokenToCookie(c, "GET",
237 "example.com/c="+arvadostest.FooCollection+"/foo",
238 "?api_token="+arvadostest.ActiveToken,
246 func (s *IntegrationSuite) TestVhostRedirectQueryTokenAttachmentOnlyHost(c *check.C) {
247 defer func(orig string) {
248 attachmentOnlyHost = orig
249 }(attachmentOnlyHost)
250 attachmentOnlyHost = "example.com:1234"
252 s.testVhostRedirectTokenToCookie(c, "GET",
253 "example.com/c="+arvadostest.FooCollection+"/foo",
254 "?api_token="+arvadostest.ActiveToken,
257 http.StatusBadRequest,
261 resp := s.testVhostRedirectTokenToCookie(c, "GET",
262 "example.com:1234/c="+arvadostest.FooCollection+"/foo",
263 "?api_token="+arvadostest.ActiveToken,
269 c.Check(resp.Header().Get("Content-Disposition"), check.Equals, "attachment")
272 func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie(c *check.C) {
273 s.testVhostRedirectTokenToCookie(c, "POST",
274 arvadostest.FooCollection+".example.com/foo",
276 "application/x-www-form-urlencoded",
277 url.Values{"api_token": {arvadostest.ActiveToken}}.Encode(),
283 func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie404(c *check.C) {
284 s.testVhostRedirectTokenToCookie(c, "POST",
285 arvadostest.FooCollection+".example.com/foo",
287 "application/x-www-form-urlencoded",
288 url.Values{"api_token": {arvadostest.SpectatorToken}}.Encode(),
294 func (s *IntegrationSuite) TestAnonymousTokenOK(c *check.C) {
295 anonymousTokens = []string{arvadostest.AnonymousToken}
296 s.testVhostRedirectTokenToCookie(c, "GET",
297 "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
306 func (s *IntegrationSuite) TestAnonymousTokenError(c *check.C) {
307 anonymousTokens = []string{"anonymousTokenConfiguredButInvalid"}
308 s.testVhostRedirectTokenToCookie(c, "GET",
309 "example.com/c="+arvadostest.HelloWorldCollection+"/Hello%20world.txt",
318 func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString, contentType, reqBody string, expectStatus int, expectRespBody string) *httptest.ResponseRecorder {
319 u, _ := url.Parse(`http://` + hostPath + queryString)
320 req := &http.Request{
324 Header: http.Header{"Content-Type": {contentType}},
325 Body: ioutil.NopCloser(strings.NewReader(reqBody)),
328 resp := httptest.NewRecorder()
330 c.Check(resp.Code, check.Equals, expectStatus)
331 c.Check(resp.Body.String(), check.Equals, expectRespBody)
334 (&handler{}).ServeHTTP(resp, req)
335 if resp.Code != http.StatusSeeOther {
338 c.Check(resp.Body.String(), check.Matches, `.*href="//`+regexp.QuoteMeta(html.EscapeString(hostPath))+`".*`)
339 cookies := (&http.Response{Header: resp.Header()}).Cookies()
341 u, _ = u.Parse(resp.Header().Get("Location"))
346 Header: http.Header{},
348 for _, c := range cookies {
352 resp = httptest.NewRecorder()
353 (&handler{}).ServeHTTP(resp, req)
354 c.Check(resp.Header().Get("Location"), check.Equals, "")