}
}
// The client's token was invalid (e.g., expired), or
- // the client didn't even provide one. Propagate the
- // 401 to encourage the client to use a [different]
- // token.
+ // the client didn't even provide one. Redirect to
+ // workbench2's login-and-redirect-to-download url if
+ // this is a browser navigation request. (The redirect
+ // flow can't preserve the original method if it's not
+ // GET, and doesn't make sense if the UA is a
+ // command-line tool, is trying to load an inline
+ // image, etc.; in these cases, there's nothing we can
+ // do, so return 401 unauthorized.)
+ //
+ // Note Sec-Fetch-Mode is sent by all non-EOL
+ // browsers, except Safari.
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-Fetch-Mode
//
// TODO(TC): This response would be confusing to
// someone trying (anonymously) to download public
// data that has been deleted. Allow a referrer to
// provide this context somehow?
- w.Header().Add("WWW-Authenticate", "Basic realm=\"collections\"")
- http.Error(w, unauthorizedMessage, http.StatusUnauthorized)
+ if r.Method == http.MethodGet && r.Header.Get("Sec-Fetch-Mode") == "navigate" {
+ target := url.URL(h.Cluster.Services.Workbench2.ExternalURL)
+ redirkey := "redirectToPreview"
+ if attachment {
+ redirkey = "redirectToDownload"
+ }
+ callback := "/c=" + collectionID + "/" + strings.Join(targetPath, "/")
+ // target.RawQuery = url.Values{redirkey:
+ // {target}}.Encode() would be the obvious
+ // thing to do here, but wb2 doesn't decode
+ // this as a query param -- it takes
+ // everything after "${redirkey}=" as the
+ // target URL. If we encode "/" as "%2F" etc.,
+ // the redirect won't work.
+ 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
}
collection, filepath = h.determineCollection(fs, filepath)
}
if collection != nil {
- log = log.WithField("collection_uuid", collection.UUID).
- WithField("collection_file_path", filepath)
- props["collection_uuid"] = collection.UUID
+ log = log.WithField("collection_file_path", filepath)
props["collection_file_path"] = filepath
- // h.determineCollection populates the collection_uuid prop with the PDH, if
- // this collection is being accessed via PDH. In that case, blank the
- // collection_uuid field so that consumers of the log entries can rely on it
- // being a UUID, or blank. The PDH remains available via the
- // portable_data_hash property.
- if props["collection_uuid"] == collection.PortableDataHash {
- props["collection_uuid"] = ""
+ // h.determineCollection populates the collection_uuid
+ // prop with the PDH, if this collection is being
+ // accessed via PDH. For logging, we use a different
+ // field depending on whether it's a UUID or PDH.
+ if len(collection.UUID) > 32 {
+ log = log.WithField("portable_data_hash", collection.UUID)
+ props["portable_data_hash"] = collection.UUID
+ } else {
+ log = log.WithField("collection_uuid", collection.UUID)
+ props["collection_uuid"] = collection.UUID
}
}
if r.Method == "PUT" || r.Method == "POST" {
}
func (h *handler) determineCollection(fs arvados.CustomFileSystem, path string) (*arvados.Collection, string) {
- segments := strings.Split(path, "/")
- var i int
- for i = 0; i < len(segments); i++ {
- dir := append([]string{}, segments[0:i]...)
- dir = append(dir, ".arvados#collection")
- f, err := fs.OpenFile(strings.Join(dir, "/"), os.O_RDONLY, 0)
- if f != nil {
- defer f.Close()
- }
+ target := strings.TrimSuffix(path, "/")
+ for {
+ fi, err := fs.Stat(target)
if err != nil {
- if !os.IsNotExist(err) {
+ return nil, ""
+ }
+ switch src := fi.Sys().(type) {
+ case *arvados.Collection:
+ return src, strings.TrimPrefix(path[len(target):], "/")
+ case *arvados.Group:
+ return nil, ""
+ default:
+ if _, ok := src.(error); ok {
return nil, ""
}
- continue
}
- // err is nil so we found it.
- decoder := json.NewDecoder(f)
- var collection arvados.Collection
- err = decoder.Decode(&collection)
- if err != nil {
+ // Try parent
+ cut := strings.LastIndexByte(target, '/')
+ if cut < 0 {
return nil, ""
}
- return &collection, strings.Join(segments[i:], "/")
+ target = target[:cut]
}
- return nil, ""
}