Merge branch 'master' into 7490-datamanager-dont-die-return-error
[arvados.git] / apps / workbench / app / controllers / collections_controller.rb
index 0610fd2e81852569c0e37da380fd3bbff51f2ac0..f8b359c89060cd9d2703b9303cffaf40f5eef54f 100644 (file)
@@ -1,4 +1,5 @@
 require "arvados/keep"
+require "uri"
 
 class CollectionsController < ApplicationController
   include ActionController::Live
@@ -119,18 +120,45 @@ class CollectionsController < ApplicationController
     # we ask the API server if the file actually exists.  This serves two
     # purposes: it lets us return a useful status code for common errors, and
     # helps us figure out which token to provide to arv-get.
+    # The order of searched tokens is important: because the anonymous user
+    # token is passed along with every API request, we have to check it first.
+    # Otherwise, it's impossible to know whether any other request succeeded
+    # because of the reader token.
     coll = nil
-    tokens = [Thread.current[:arvados_api_token],
+    tokens = [(Rails.configuration.anonymous_user_token || nil),
               params[:reader_token],
-              (Rails.configuration.anonymous_user_token || nil)].compact
+              Thread.current[:arvados_api_token]].compact
     usable_token = find_usable_token(tokens) do
       coll = Collection.find(params[:uuid])
     end
+    if usable_token.nil?
+      # Response already rendered.
+      return
+    end
+
+    # If we are configured to use a keep-web server, just redirect to
+    # the appropriate URL.
+    if Rails.configuration.keep_web_url or
+        Rails.configuration.keep_web_download_url
+      opts = {}
+      if usable_token == params[:reader_token]
+        opts[:path_token] = usable_token
+      elsif usable_token == Rails.configuration.anonymous_user_token
+        # Don't pass a token at all
+      else
+        # We pass the current user's real token only if it's necessary
+        # to read the collection.
+        opts[:query_token] = usable_token
+      end
+      opts[:disposition] = params[:disposition] if params[:disposition]
+      return redirect_to keep_web_url(params[:uuid], params[:file], opts)
+    end
+
+    # No keep-web server available. Get the file data with arv-get,
+    # and serve it through Rails.
 
     file_name = params[:file].andand.sub(/^(\.\/|\/|)/, './')
-    if usable_token.nil?
-      return  # Response already rendered.
-    elsif file_name.nil? or not coll.manifest.has_file?(file_name)
+    if file_name.nil? or not coll.manifest.has_file?(file_name)
       return render_not_found
     end
 
@@ -269,15 +297,6 @@ class CollectionsController < ApplicationController
     sharing_popup
   end
 
-  def update
-    @updates ||= params[@object.resource_param_name.to_sym]
-    if @updates && (@updates.keys - ["name", "description"]).empty?
-      # exclude manifest_text since only name or description is being updated
-      @object.manifest_text = nil
-    end
-    super
-  end
-
   protected
 
   def find_usable_token(token_list)
@@ -310,6 +329,61 @@ class CollectionsController < ApplicationController
     return nil
   end
 
+  def keep_web_url(uuid_or_pdh, file, opts)
+    munged_id = uuid_or_pdh.sub('+', '-')
+    fmt = {uuid_or_pdh: munged_id}
+
+    tmpl = Rails.configuration.keep_web_url
+    if Rails.configuration.keep_web_download_url and
+        (!tmpl or opts[:disposition] == 'attachment')
+      # Prefer the attachment-only-host when we want an attachment
+      # (and when there is no preview link configured)
+      tmpl = Rails.configuration.keep_web_download_url
+    else
+      check_uri = URI.parse(tmpl % fmt)
+      if opts[:query_token] and
+          not check_uri.host.start_with?(munged_id + "--") and
+          not check_uri.host.start_with?(munged_id + ".")
+        # We're about to pass a token in the query string, but
+        # keep-web can't accept that safely at a single-origin URL
+        # template (unless it's -attachment-only-host).
+        tmpl = Rails.configuration.keep_web_download_url
+        if not tmpl
+          raise ArgumentError, "Download precluded by site configuration"
+        end
+        logger.warn("Using download link, even though inline content " \
+                    "was requested: #{check_uri.to_s}")
+      end
+    end
+
+    if tmpl == Rails.configuration.keep_web_download_url
+      # This takes us to keep-web's -attachment-only-host so there is
+      # no need to add ?disposition=attachment.
+      opts.delete :disposition
+    end
+
+    uri = URI.parse(tmpl % fmt)
+    uri.path += '/' unless uri.path.end_with? '/'
+    if opts[:path_token]
+      uri.path += 't=' + opts[:path_token] + '/'
+    end
+    uri.path += '_/'
+    uri.path += URI.escape(file)
+
+    query = Hash[URI.decode_www_form(uri.query || '')]
+    { query_token: 'api_token',
+      disposition: 'disposition' }.each do |opt, param|
+      if opts.include? opt
+        query[param] = opts[opt]
+      end
+    end
+    unless query.empty?
+      uri.query = URI.encode_www_form(query)
+    end
+
+    uri.to_s
+  end
+
   # Note: several controller and integration tests rely on stubbing
   # file_enumerator to return fake file content.
   def file_enumerator opts