Merge branch 'master' into 3112-report-bug
[arvados.git] / services / api / app / controllers / arvados / v1 / collections_controller.rb
index 8db93c36c2171fa310e6939ae00ddd830dd06ee7..b65fa5b962ab76b9c26e52a0443cb4586f90a043 100644 (file)
@@ -10,22 +10,61 @@ class Arvados::V1::CollectionsController < ApplicationController
       logger.warn "User #{current_user.andand.uuid} tried to set collection owner_uuid to #{owner_uuid}"
       raise ArvadosModel::PermissionDeniedError
     end
+
+    # Check permissions on the collection manifest.
+    # If any signature cannot be verified, return 403 Permission denied.
+    api_token = current_api_client_authorization.andand.api_token
+    signing_opts = {
+      key: Rails.configuration.blob_signing_key,
+      api_token: api_token,
+      ttl: Rails.configuration.blob_signing_ttl,
+    }
+    resource_attrs[:manifest_text].lines.each do |entry|
+      entry.split[1..-1].each do |tok|
+        if /^[[:digit:]]+:[[:digit:]]+:/.match tok
+          # This is a filename token, not a blob locator. Note that we
+          # keep checking tokens after this, even though manifest
+          # format dictates that all subsequent tokens will also be
+          # filenames. Safety first!
+        elsif Blob.verify_signature tok, signing_opts
+          # OK.
+        elsif Locator.parse(tok).andand.signature
+          # Signature provided, but verify_signature did not like it.
+          logger.warn "Invalid signature on locator #{tok}"
+          raise ArvadosModel::PermissionDeniedError
+        elsif Rails.configuration.permit_create_collection_with_unsigned_manifest
+          # No signature provided, but we are running in insecure mode.
+          logger.debug "Missing signature on locator #{tok} ignored"
+        elsif Blob.new(tok).empty?
+          # No signature provided -- but no data to protect, either.
+        else
+          logger.warn "Missing signature on locator #{tok}"
+          raise ArvadosModel::PermissionDeniedError
+        end
+      end
+    end
+
+    # Remove any permission signatures from the manifest.
+    munge_manifest_locators(resource_attrs[:manifest_text]) do |loc|
+      loc.without_signature.to_s
+    end
+
+    # Save the collection with the stripped manifest.
     act_as_system_user do
       @object = model_class.new resource_attrs.reject { |k,v| k == :owner_uuid }
       begin
         @object.save!
       rescue ActiveRecord::RecordNotUnique
         logger.debug resource_attrs.inspect
-        if resource_attrs[:manifest_text] and resource_attrs[:uuid]
+        if @object.manifest_text and @object.uuid
           @existing_object = model_class.
             where('uuid=? and manifest_text=?',
-                  resource_attrs[:uuid],
-                  resource_attrs[:manifest_text]).
+                  @object.uuid,
+                  @object.manifest_text).
             first
           @object = @existing_object || @object
         end
       end
-
       if @object
         link_attrs = {
           owner_uuid: owner_uuid,
@@ -45,7 +84,13 @@ class Arvados::V1::CollectionsController < ApplicationController
   end
 
   def show
-    render json: @object.as_api_response(:with_data)
+    sign_manifests(@object[:manifest_text])
+    super
+  end
+
+  def index
+    sign_manifests(*@objects.map { |c| c[:manifest_text] })
+    super
   end
 
   def collection_uuid(uuid)
@@ -86,7 +131,7 @@ class Arvados::V1::CollectionsController < ApplicationController
 
     logger.debug "visiting #{uuid}"
 
-    if m  
+    if m
       # uuid is a collection
       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
         visited[uuid] = c.as_api_response
@@ -103,7 +148,7 @@ class Arvados::V1::CollectionsController < ApplicationController
       Job.readable_by(current_user).where(log: uuid).each do |job|
         generate_provenance_edges(visited, job.uuid)
       end
-      
+
     else
       # uuid is something else
       rsc = ArvadosModel::resource_class_for_uuid uuid
@@ -145,7 +190,7 @@ class Arvados::V1::CollectionsController < ApplicationController
 
     logger.debug "visiting #{uuid}"
 
-    if m  
+    if m
       # uuid is a collection
       Collection.readable_by(current_user).where(uuid: uuid).each do |c|
         visited[uuid] = c.as_api_response
@@ -163,7 +208,7 @@ class Arvados::V1::CollectionsController < ApplicationController
       Job.readable_by(current_user).where(["jobs.script_parameters like ?", "%#{uuid}%"]).each do |job|
         generate_used_by_edges(visited, job.uuid)
       end
-      
+
     else
       # uuid is something else
       rsc = ArvadosModel::resource_class_for_uuid uuid
@@ -195,7 +240,27 @@ class Arvados::V1::CollectionsController < ApplicationController
     render json: visited
   end
 
+  def self.munge_manifest_locators(manifest)
+    # Given a manifest text and a block, yield each locator,
+    # and replace it with whatever the block returns.
+    manifest.andand.gsub!(/ [[:xdigit:]]{32}(\+[[:digit:]]+)?(\+\S+)/) do |word|
+      if loc = Locator.parse(word.strip)
+        " " + yield(loc)
+      else
+        " " + word
+      end
+    end
+  end
+
   protected
+
+  def find_objects_for_index
+    # Omit manifest_text from index results unless expressly selected.
+    @select ||= model_class.api_accessible_attributes(:user).
+      map { |attr_spec| attr_spec.first.to_s } - ["manifest_text"]
+    super
+  end
+
   def find_object_by_uuid
     super
     if !@object and !params[:uuid].match(/^[0-9a-f]+\+\d+$/)
@@ -215,4 +280,22 @@ class Arvados::V1::CollectionsController < ApplicationController
     end
   end
 
+  def munge_manifest_locators(manifest, &block)
+    self.class.munge_manifest_locators(manifest, &block)
+  end
+
+  def sign_manifests(*manifests)
+    if current_api_client_authorization
+      signing_opts = {
+        key: Rails.configuration.blob_signing_key,
+        api_token: current_api_client_authorization.api_token,
+        ttl: Rails.configuration.blob_signing_ttl,
+      }
+      manifests.each do |text|
+        munge_manifest_locators(text) do |loc|
+          Blob.sign_locator(loc.to_s, signing_opts)
+        end
+      end
+    end
+  end
 end