Merge branch '11917-dont-clear-cache'
[arvados.git] / apps / workbench / app / controllers / collections_controller.rb
index d4ea86c535913f7e688b494db7cf23631735968c..f8fcf5108f025659bf5058f2861ef42d2e1b5781 100644 (file)
@@ -1,4 +1,10 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 require "arvados/keep"
+require "arvados/collection"
+require "uri"
 
 class CollectionsController < ApplicationController
   include ActionController::Live
@@ -18,7 +24,7 @@ class CollectionsController < ApplicationController
   RELATION_LIMIT = 5
 
   def show_pane_list
-    panes = %w(Files Upload Provenance_graph Used_by Advanced)
+    panes = %w(Files Upload Tags Provenance_graph Used_by Advanced)
     panes = panes - %w(Upload) unless (@object.editable? rescue false)
     panes
   end
@@ -109,6 +115,10 @@ class CollectionsController < ApplicationController
   end
 
   def show_file_links
+    if Rails.configuration.keep_web_url || Rails.configuration.keep_web_download_url
+      # show_file will redirect to keep-web's directory listing
+      return show_file
+    end
     Thread.current[:reader_tokens] = [params[:reader_token]]
     return if false.equal?(find_object_by_uuid)
     render layout: false
@@ -130,11 +140,34 @@ class CollectionsController < ApplicationController
     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
 
@@ -196,7 +229,7 @@ class CollectionsController < ApplicationController
     if params["tab_pane"] == "Provenance_graph"
       @prov_svg = ProvenanceHelper::create_provenance_graph(@object.provenance, "provenance_svg",
                                                             {:request => request,
-                                                             :direction => :bottom_up,
+                                                             :direction => :top_down,
                                                              :combine_jobs => :script_only}) rescue nil
     end
 
@@ -215,12 +248,15 @@ class CollectionsController < ApplicationController
         render 'hash_matches'
         return
       else
-        jobs_with = lambda do |conds|
-          Job.limit(RELATION_LIMIT).where(conds)
-            .results.sort_by { |j| j.finished_at || j.created_at }
+        if Job.api_exists?(:index)
+          jobs_with = lambda do |conds|
+            Job.limit(RELATION_LIMIT).where(conds)
+              .results.sort_by { |j| j.finished_at || j.created_at }
+          end
+          @output_of = jobs_with.call(output: @object.portable_data_hash)
+          @log_of = jobs_with.call(log: @object.portable_data_hash)
         end
-        @output_of = jobs_with.call(output: @object.portable_data_hash)
-        @log_of = jobs_with.call(log: @object.portable_data_hash)
+
         @project_links = Link.limit(RELATION_LIMIT).order("modified_at DESC")
           .where(head_uuid: @object.uuid, link_class: 'name').results
         project_hash = Group.where(uuid: @project_links.map(&:tail_uuid)).to_hash
@@ -229,21 +265,14 @@ class CollectionsController < ApplicationController
         @permissions = Link.limit(RELATION_LIMIT).order("modified_at DESC")
           .where(head_uuid: @object.uuid, link_class: 'permission',
                  name: 'can_read').results
-        @logs = Log.limit(RELATION_LIMIT).order("created_at DESC")
-          .select(%w(uuid event_type object_uuid event_at summary))
-          .where(object_uuid: @object.uuid).results
-        @is_persistent = Link.limit(1)
-          .where(head_uuid: @object.uuid, tail_uuid: current_user.uuid,
-                 link_class: 'resources', name: 'wants')
-          .results.any?
         @search_sharing = search_scopes
 
         if params["tab_pane"] == "Used_by"
           @used_by_svg = ProvenanceHelper::create_provenance_graph(@object.used_by, "used_by_svg",
                                                                    {:request => request,
-                                                                     :direction => :top_down,
-                                                                     :combine_jobs => :script_only,
-                                                                     :pdata_only => true}) rescue nil
+                                                                    :direction => :top_down,
+                                                                    :combine_jobs => :script_only,
+                                                                    :pdata_only => true}) rescue nil
         end
       end
     end
@@ -258,7 +287,12 @@ class CollectionsController < ApplicationController
   helper_method :download_link
 
   def download_link
-    collections_url + "/download/#{@object.uuid}/#{@search_sharing.first.api_token}/"
+    token = @search_sharing.first.api_token
+    if Rails.configuration.keep_web_url || Rails.configuration.keep_web_download_url
+      keep_web_url(@object.uuid, nil, {path_token: token})
+    else
+      collections_url + "/download/#{@object.uuid}/#{token}/"
+    end
   end
 
   def share
@@ -273,13 +307,79 @@ class CollectionsController < ApplicationController
     sharing_popup
   end
 
+  def remove_selected_files
+    uuids, source_paths = selected_collection_files params
+
+    arv_coll = Arv::Collection.new(@object.manifest_text)
+    source_paths[uuids[0]].each do |p|
+      arv_coll.rm "."+p
+    end
+
+    if @object.update_attributes manifest_text: arv_coll.manifest_text
+      show
+    else
+      self.render_error status: 422
+    end
+  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
+    updated_attr = params[:collection].each.select {|a| a[0].andand.start_with? 'rename-file-path:'}
+
+    if updated_attr.size > 0
+      # Is it file rename?
+      file_path = updated_attr[0][0].split('rename-file-path:')[-1]
+
+      new_file_path = updated_attr[0][1]
+      if new_file_path.start_with?('./')
+        # looks good
+      elsif new_file_path.start_with?('/')
+        new_file_path = '.' + new_file_path
+      else
+        new_file_path = './' + new_file_path
+      end
+
+      arv_coll = Arv::Collection.new(@object.manifest_text)
+
+      if arv_coll.exist?(new_file_path)
+        @errors = 'Duplicate file path. Please use a different name.'
+        self.render_error status: 422
+      else
+        arv_coll.rename "./"+file_path, new_file_path
+
+        if @object.update_attributes manifest_text: arv_coll.manifest_text
+          show
+        else
+          self.render_error status: 422
+        end
+      end
+    else
+      # Not a file rename; use default
+      super
+    end
+  end
+
+  def tags
+    render
+  end
+
+  def save_tags
+    tags_param = params['tag_data']
+    if tags_param
+      if tags_param.is_a?(String) && tags_param == "empty"
+        tags = {}
+      else
+        tags = tags_param
+      end
+    end
+
+    if tags
+      if @object.update_attributes properties: tags
+        @saved_tags = true
+        render
+      else
+        self.render_error status: 422
+      end
     end
-    super
   end
 
   protected
@@ -314,6 +414,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
+    elsif not Rails.configuration.trust_all_content
+      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) if 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