var urlPDHDecoder = strings.NewReplacer(" ", "+", "-", "+")
var notFoundMessage = "Not Found"
-var unauthorizedMessage = "401 Unauthorized\r\n\r\nA valid Arvados token must be provided to access this resource.\r\n"
+var unauthorizedMessage = "401 Unauthorized\n\nA valid Arvados token must be provided to access this resource."
// parseCollectionIDFromURL returns a UUID or PDH if s is a UUID or a
// PDH (even if it is a PDH with "+" replaced by " " or "-");
}
}
- if tokens == nil {
- if !credentialsOK {
- http.Error(w, fmt.Sprintf("Authorization tokens are not accepted here: %v, and no anonymous user token is configured.", reasonNotAcceptingCredentials), http.StatusUnauthorized)
- } else {
- http.Error(w, fmt.Sprintf("No authorization token in request, and no anonymous user token is configured."), http.StatusUnauthorized)
- }
- return
- }
-
if len(targetPath) > 0 && targetPath[0] == "_" {
// If a collection has a directory called "t=foo" or
// "_", it can be served at
dirOpenMode = os.O_RDWR
}
- validToken := make(map[string]bool)
+ var tokenValid bool
+ var tokenScopeProblem bool
var token string
var tokenUser *arvados.User
var sessionFS arvados.CustomFileSystem
http.Error(w, "cache error: "+err.Error(), http.StatusInternalServerError)
return
}
+ if token != h.Cluster.Users.AnonymousUserToken {
+ tokenValid = true
+ }
f, err := fs.OpenFile(fsprefix, dirOpenMode, 0)
- if errors.As(err, &statusErr) && statusErr.HTTPStatus() == http.StatusForbidden {
- // collection id is outside token scope
- validToken[token] = true
+ if errors.As(err, &statusErr) &&
+ statusErr.HTTPStatus() == http.StatusForbidden &&
+ token != h.Cluster.Users.AnonymousUserToken {
+ // collection id is outside scope of supplied
+ // token
+ tokenScopeProblem = true
continue
- }
- validToken[token] = true
- if os.IsNotExist(err) {
+ } else if os.IsNotExist(err) {
// collection does not exist or is not
// readable using this token
continue
}
}
if session == nil {
- if pathToken || !credentialsOK {
- // Either the URL is a "secret sharing link"
- // that didn't work out (and asking the client
- // for additional credentials would just be
- // confusing), or we don't even accept
- // credentials at this path.
+ if pathToken {
+ // The URL is a "secret sharing link" that
+ // didn't work out. Asking the client for
+ // additional credentials would just be
+ // confusing.
http.Error(w, notFoundMessage, http.StatusNotFound)
return
}
- for _, t := range reqTokens {
- if validToken[t] {
- // The client provided valid token(s),
- // but the collection was not found.
- http.Error(w, notFoundMessage, http.StatusNotFound)
- return
- }
+ if tokenValid {
+ // The client provided valid token(s), but the
+ // collection was not found.
+ http.Error(w, notFoundMessage, http.StatusNotFound)
+ return
+ }
+ if tokenScopeProblem {
+ // The client provided a valid token but
+ // fetching a collection returned 401, which
+ // means the token scope doesn't permit
+ // fetching that collection.
+ http.Error(w, notFoundMessage, http.StatusForbidden)
+ return
}
// The client's token was invalid (e.g., expired), or
// the client didn't even provide one. Redirect to
target.RawQuery = redirkey + "=" + callback
w.Header().Add("Location", target.String())
w.WriteHeader(http.StatusSeeOther)
- } else {
- w.Header().Add("WWW-Authenticate", "Basic realm=\"collections\"")
- http.Error(w, unauthorizedMessage, http.StatusUnauthorized)
+ return
+ }
+ if !credentialsOK {
+ http.Error(w, fmt.Sprintf("Authorization tokens are not accepted here: %v, and no anonymous user token is configured.", reasonNotAcceptingCredentials), http.StatusUnauthorized)
+ return
}
+ // If none of the above cases apply, suggest the
+ // user-agent (which is either a non-browser agent
+ // like wget, or a browser that can't redirect through
+ // a login flow) prompt the user for credentials.
+ w.Header().Add("WWW-Authenticate", "Basic realm=\"collections\"")
+ http.Error(w, unauthorizedMessage, http.StatusUnauthorized)
return
}
nil,
"",
http.StatusNotFound,
- notFoundMessage+"\n",
+ regexp.QuoteMeta(notFoundMessage+"\n"),
)
}
nil,
"",
http.StatusNotFound,
- notFoundMessage+"\n",
+ regexp.QuoteMeta(notFoundMessage+"\n"),
)
}
http.Header{"Sec-Fetch-Mode": {"cors"}},
"",
http.StatusUnauthorized,
- unauthorizedMessage+"\n",
+ regexp.QuoteMeta(unauthorizedMessage+"\n"),
)
s.testVhostRedirectTokenToCookie(c, "GET",
s.handler.Cluster.Services.WebDAVDownload.ExternalURL.Host+"/c="+arvadostest.FooCollection+"/foo",
nil,
"",
http.StatusUnauthorized,
- unauthorizedMessage+"\n",
+ regexp.QuoteMeta(unauthorizedMessage+"\n"),
)
}
nil,
"",
http.StatusBadRequest,
- "cannot serve inline content at this URL (possible configuration error; see https://doc.arvados.org/install/install-keep-web.html#dns)\n",
+ regexp.QuoteMeta("cannot serve inline content at this URL (possible configuration error; see https://doc.arvados.org/install/install-keep-web.html#dns)\n"),
)
}
nil,
"",
http.StatusBadRequest,
- "cannot serve inline content at this URL (possible configuration error; see https://doc.arvados.org/install/install-keep-web.html#dns)\n",
+ regexp.QuoteMeta("cannot serve inline content at this URL (possible configuration error; see https://doc.arvados.org/install/install-keep-web.html#dns)\n"),
)
resp := s.testVhostRedirectTokenToCookie(c, "GET",
http.Header{"Content-Type": {"application/x-www-form-urlencoded"}},
url.Values{"api_token": {arvadostest.SpectatorToken}}.Encode(),
http.StatusNotFound,
- notFoundMessage+"\n",
+ regexp.QuoteMeta(notFoundMessage+"\n"),
)
}
"",
nil,
"",
- http.StatusNotFound,
- notFoundMessage+"\n",
+ http.StatusUnauthorized,
+ "Authorization tokens are not accepted here: .*\n",
)
}
base := "http://download.example.com/by_id/" + coll.OwnerUUID + "/"
for tryURL, expectRegexp := range map[string]string{
- base: `(?ms).*href="./` + nameShownEscaped + `/"\S+` + nameShown + `.*`,
+ base: `(?ms).*href="./` + nameShownEscaped + `/"\S+` + nameShown + `.*`,
base + nameShownEscaped + "/": `(?ms).*href="./filename"\S+filename.*`,
} {
u, _ := url.Parse(tryURL)
c.Check(resp.Header().Get("Access-Control-Allow-Origin"), check.Equals, "*")
}
-func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString string, reqHeader http.Header, reqBody string, expectStatus int, expectRespBody string) *httptest.ResponseRecorder {
+func (s *IntegrationSuite) testVhostRedirectTokenToCookie(c *check.C, method, hostPath, queryString string, reqHeader http.Header, reqBody string, expectStatus int, matchRespBody string) *httptest.ResponseRecorder {
if reqHeader == nil {
reqHeader = http.Header{}
}
resp := httptest.NewRecorder()
defer func() {
c.Check(resp.Code, check.Equals, expectStatus)
- c.Check(resp.Body.String(), check.Equals, expectRespBody)
+ c.Check(resp.Body.String(), check.Matches, matchRespBody)
}()
s.handler.ServeHTTP(resp, req)
c.Check(req.URL.Path, check.Equals, trial.redirect, comment)
}
if trial.expect == nil {
- if s.handler.Cluster.Users.AnonymousUserToken == "" {
- c.Check(resp.Code, check.Equals, http.StatusUnauthorized, comment)
- } else {
- c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
- }
+ c.Check(resp.Code, check.Equals, http.StatusUnauthorized, comment)
} else {
c.Check(resp.Code, check.Equals, http.StatusOK, comment)
for _, e := range trial.expect {
resp = httptest.NewRecorder()
s.handler.ServeHTTP(resp, req)
if trial.expect == nil {
- if s.handler.Cluster.Users.AnonymousUserToken == "" {
- c.Check(resp.Code, check.Equals, http.StatusUnauthorized, comment)
- } else {
- c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
- }
+ c.Check(resp.Code, check.Equals, http.StatusUnauthorized, comment)
} else {
c.Check(resp.Code, check.Equals, http.StatusOK, comment)
}
resp = httptest.NewRecorder()
s.handler.ServeHTTP(resp, req)
if trial.expect == nil {
- if s.handler.Cluster.Users.AnonymousUserToken == "" {
- c.Check(resp.Code, check.Equals, http.StatusUnauthorized, comment)
- } else {
- c.Check(resp.Code, check.Equals, http.StatusNotFound, comment)
- }
+ c.Check(resp.Code, check.Equals, http.StatusUnauthorized, comment)
} else {
c.Check(resp.Code, check.Equals, http.StatusMultiStatus, comment)
for _, e := range trial.expect {
"",
"bogustoken",
} {
- hdr, body, _ := s.runCurl(c, token, "collections.example.com", "/collections/"+arvadostest.FooCollection+"/foo")
- c.Check(hdr, check.Matches, `(?s)HTTP/1.1 404 Not Found\r\n.*`)
- c.Check(strings.TrimSpace(body), check.Equals, notFoundMessage)
+ hdr, body, _ := s.runCurl(c, token, s.handler.Cluster.Services.WebDAVDownload.ExternalURL.Host, "/c="+arvadostest.FooCollection+"/foo")
+ c.Check(hdr, check.Matches, `(?s)HTTP/1.1 401 Unauthorized\r\n.*`)
+ c.Check(strings.TrimSpace(body), check.Equals, unauthorizedMessage)
if token != "" {
- hdr, body, _ = s.runCurl(c, token, "collections.example.com", "/collections/download/"+arvadostest.FooCollection+"/"+token+"/foo")
+ hdr, body, _ = s.runCurl(c, token, s.handler.Cluster.Services.WebDAVDownload.ExternalURL.Host, "/collections/download/"+arvadostest.FooCollection+"/"+token+"/foo")
c.Check(hdr, check.Matches, `(?s)HTTP/1.1 404 Not Found\r\n.*`)
c.Check(strings.TrimSpace(body), check.Equals, notFoundMessage)
}
- hdr, body, _ = s.runCurl(c, token, "collections.example.com", "/bad-route")
+ hdr, body, _ = s.runCurl(c, token, s.handler.Cluster.Services.WebDAVDownload.ExternalURL.Host, "/bad-route")
c.Check(hdr, check.Matches, `(?s)HTTP/1.1 404 Not Found\r\n.*`)
c.Check(strings.TrimSpace(body), check.Equals, notFoundMessage)
}
"/download",
"/collections",
"/collections/",
- // Implicit/generated index is not implemented yet;
- // until then, return 404.
- "/collections/" + arvadostest.FooCollection,
- "/collections/" + arvadostest.FooCollection + "/",
- "/collections/" + arvadostest.FooBarDirCollection + "/dir1",
- "/collections/" + arvadostest.FooBarDirCollection + "/dir1/",
- // Non-existent file in collection
- "/collections/" + arvadostest.FooCollection + "/theperthcountyconspiracy",
+ // Non-existent file/directory
+ "/c=" + arvadostest.FooCollection + "/theperthcountyconspiracy",
+ "/c=" + arvadostest.FooCollection + "/theperthcountyconspiracy/",
"/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
+ "/collections/download/" + arvadostest.FooCollection + "/" + arvadostest.ActiveToken + "/theperthcountyconspiracy/",
// Non-existent collection
- "/collections/" + arvadostest.NonexistentCollection,
- "/collections/" + arvadostest.NonexistentCollection + "/",
- "/collections/" + arvadostest.NonexistentCollection + "/theperthcountyconspiracy",
+ "/c=" + arvadostest.NonexistentCollection,
+ "/c=" + arvadostest.NonexistentCollection + "/",
+ "/c=" + arvadostest.NonexistentCollection + "/theperthcountyconspiracy",
"/collections/download/" + arvadostest.NonexistentCollection + "/" + arvadostest.ActiveToken + "/theperthcountyconspiracy",
} {
- hdr, body, _ := s.runCurl(c, arvadostest.ActiveToken, "collections.example.com", uri)
+ hdr, body, _ := s.runCurl(c, arvadostest.ActiveToken, s.handler.Cluster.Services.WebDAVDownload.ExternalURL.Host, uri)
c.Check(hdr, check.Matches, "(?s)HTTP/1.1 404 Not Found\r\n.*")
if len(body) > 0 {
c.Check(strings.TrimSpace(body), check.Equals, notFoundMessage)
}
// Return header block and body.
-func (s *IntegrationSuite) runCurl(c *check.C, auth, host, uri string, args ...string) (hdr, bodyPart string, bodySize int64) {
+func (s *IntegrationSuite) runCurl(c *check.C, auth, hostport, uri string, args ...string) (hdr, bodyPart string, bodySize int64) {
curlArgs := []string{"--silent", "--show-error", "--include"}
testHost, testPort, _ := net.SplitHostPort(s.testServer.URL[7:])
- curlArgs = append(curlArgs, "--resolve", host+":"+testPort+":"+testHost)
+ host, port, _ := net.SplitHostPort(hostport)
+ if port == "" {
+ port = "80"
+ }
+ curlArgs = append(curlArgs, "--connect-to", host+":"+port+":"+testHost+":"+testPort)
if strings.Contains(auth, " ") {
// caller supplied entire Authorization header value
curlArgs = append(curlArgs, "-H", "Authorization: "+auth)
curlArgs = append(curlArgs, "-H", "Authorization: Bearer "+auth)
}
curlArgs = append(curlArgs, args...)
- curlArgs = append(curlArgs, "http://"+host+":"+testPort+uri)
+ curlArgs = append(curlArgs, "http://"+hostport+uri)
c.Log(fmt.Sprintf("curlArgs == %#v", curlArgs))
cmd := exec.Command("curl", curlArgs...)
stdout, err := cmd.StdoutPipe()