package main import ( "html" "io/ioutil" "net/http" "net/http/httptest" "net/url" "regexp" "strings" "git.curoverse.com/arvados.git/sdk/go/arvadostest" "git.curoverse.com/arvados.git/sdk/go/auth" check "gopkg.in/check.v1" ) var _ = check.Suite(&UnitSuite{}) type UnitSuite struct {} func mustParseURL(s string) *url.URL { r, err := url.Parse(s) if err != nil { panic("parse URL: " + s) } return r } func (s *IntegrationSuite) TestVhost404(c *check.C) { for _, testURL := range []string{ arvadostest.NonexistentCollection + ".example.com/theperthcountyconspiracy", arvadostest.NonexistentCollection + ".example.com/t=" + arvadostest.ActiveToken + "/theperthcountyconspiracy", } { resp := httptest.NewRecorder() req := &http.Request{ Method: "GET", URL: mustParseURL(testURL), } (&handler{}).ServeHTTP(resp, req) c.Check(resp.Code, check.Equals, http.StatusNotFound) c.Check(resp.Body.String(), check.Equals, "") } } // An authorizer modifies an HTTP request to make use of the given // token -- by adding it to a header, cookie, query param, or whatever // -- and returns the HTTP status code we should expect from keep-web if // the token is invalid. type authorizer func(*http.Request, string) int func (s *IntegrationSuite) TestVhostViaAuthzHeader(c *check.C) { doVhostRequests(c, authzViaAuthzHeader) } func authzViaAuthzHeader(r *http.Request, tok string) int { r.Header.Add("Authorization", "OAuth2 " + tok) return http.StatusUnauthorized } func (s *IntegrationSuite) TestVhostViaCookieValue(c *check.C) { doVhostRequests(c, authzViaCookieValue) } func authzViaCookieValue(r *http.Request, tok string) int { r.AddCookie(&http.Cookie{ Name: "api_token", Value: auth.EncodeTokenCookie([]byte(tok)), }) return http.StatusUnauthorized } func (s *IntegrationSuite) TestVhostViaPath(c *check.C) { doVhostRequests(c, authzViaPath) } func authzViaPath(r *http.Request, tok string) int { r.URL.Path = "/t=" + tok + r.URL.Path return http.StatusNotFound } func (s *IntegrationSuite) TestVhostViaQueryString(c *check.C) { doVhostRequests(c, authzViaQueryString) } func authzViaQueryString(r *http.Request, tok string) int { r.URL.RawQuery = "api_token=" + tok return http.StatusUnauthorized } func (s *IntegrationSuite) TestVhostViaPOST(c *check.C) { doVhostRequests(c, authzViaPOST) } func authzViaPOST(r *http.Request, tok string) int { r.Method = "POST" r.Header.Add("Content-Type", "application/x-www-form-urlencoded") r.Body = ioutil.NopCloser(strings.NewReader( url.Values{"api_token": {tok}}.Encode())) return http.StatusUnauthorized } // Try some combinations of {url, token} using the given authorization // mechanism, and verify the result is correct. func doVhostRequests(c *check.C, authz authorizer) { for _, hostPath := range []string{ arvadostest.FooCollection + ".example.com/foo", arvadostest.FooCollection + "--dl.example.com/foo", arvadostest.FooCollection + "--dl.example.com/_/foo", arvadostest.FooPdh + ".example.com/foo", strings.Replace(arvadostest.FooPdh, "+", "-", -1) + "--dl.example.com/foo", } { c.Log("doRequests: ", hostPath) doVhostRequestsWithHostPath(c, authz, hostPath) } } func doVhostRequestsWithHostPath(c *check.C, authz authorizer, hostPath string) { for _, tok := range []string{ arvadostest.ActiveToken, arvadostest.ActiveToken[:15], arvadostest.SpectatorToken, "bogus", "", } { u := mustParseURL("http://" + hostPath) req := &http.Request{ Method: "GET", Host: u.Host, URL: u, Header: http.Header{}, } failCode := authz(req, tok) resp := doReq(req) code, body := resp.Code, resp.Body.String() if tok == arvadostest.ActiveToken { c.Check(code, check.Equals, http.StatusOK) c.Check(body, check.Equals, "foo") } else { c.Check(code >= 400, check.Equals, true) c.Check(code < 500, check.Equals, true) if tok == arvadostest.SpectatorToken { // Valid token never offers to retry // with different credentials. c.Check(code, check.Equals, http.StatusNotFound) } else { // Invalid token can ask to retry // depending on the authz method. c.Check(code, check.Equals, failCode) } c.Check(body, check.Equals, "") } } } func doReq(req *http.Request) *httptest.ResponseRecorder { resp := httptest.NewRecorder() (&handler{}).ServeHTTP(resp, req) if resp.Code != http.StatusSeeOther { return resp } cookies := (&http.Response{Header: resp.Header()}).Cookies() u, _ := req.URL.Parse(resp.Header().Get("Location")) req = &http.Request{ Method: "GET", Host: u.Host, URL: u, Header: http.Header{}, } for _, c := range cookies { req.AddCookie(c) } return doReq(req) } func (s *IntegrationSuite) TestVhostRedirectQueryTokenToCookie(c *check.C) { s.testVhostRedirectTokenToCookie(c, "GET", arvadostest.FooCollection + ".example.com/foo", "?api_token=" + arvadostest.ActiveToken, "text/plain", "", http.StatusOK, ) } func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie(c *check.C) { s.testVhostRedirectTokenToCookie(c, "POST", arvadostest.FooCollection + ".example.com/foo", "", "application/x-www-form-urlencoded", url.Values{"api_token": {arvadostest.ActiveToken}}.Encode(), http.StatusOK, ) } func (s *IntegrationSuite) TestVhostRedirectPOSTFormTokenToCookie404(c *check.C) { s.testVhostRedirectTokenToCookie(c, "POST", arvadostest.FooCollection + ".example.com/foo", "", "application/x-www-form-urlencoded", url.Values{"api_token": {arvadostest.SpectatorToken}}.Encode(), http.StatusNotFound, ) } func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString, contentType, body string, expectStatus int) { u, _ := url.Parse(`http://` + hostPath + queryString) req := &http.Request{ Method: method, Host: u.Host, URL: u, Header: http.Header{"Content-Type": {contentType}}, Body: ioutil.NopCloser(strings.NewReader(body)), } resp := httptest.NewRecorder() (&handler{}).ServeHTTP(resp, req) c.Assert(resp.Code, check.Equals, http.StatusSeeOther) c.Check(resp.Body.String(), check.Matches, `.*href="//` + regexp.QuoteMeta(html.EscapeString(hostPath)) + `".*`) cookies := (&http.Response{Header: resp.Header()}).Cookies() u, _ = u.Parse(resp.Header().Get("Location")) req = &http.Request{ Method: "GET", Host: u.Host, URL: u, Header: http.Header{}, } for _, c := range cookies { req.AddCookie(c) } resp = httptest.NewRecorder() (&handler{}).ServeHTTP(resp, req) c.Check(resp.Header().Get("Location"), check.Equals, "") c.Check(resp.Code, check.Equals, expectStatus) if expectStatus == http.StatusOK { c.Check(resp.Body.String(), check.Equals, "foo") } }