Merge branch '16265-security-updates' into dependabot/bundler/apps/workbench/rake...
[arvados.git] / services / api / app / models / collection.rb
index 739be4ec93d0390e5bb1f7097d9e767be5bedbe8..caac5611e79c8baa43d30e396b33cc4a92f9d146 100644 (file)
@@ -22,21 +22,25 @@ class Collection < ArvadosModel
 
   before_validation :default_empty_manifest
   before_validation :default_storage_classes, on: :create
+  before_validation :managed_properties, on: :create
   before_validation :check_encoding
   before_validation :check_manifest_validity
   before_validation :check_signatures
   before_validation :strip_signatures_and_update_replication_confirmed
+  before_validation :name_null_if_empty
+  validate :ensure_filesystem_compatible_name
   validate :ensure_pdh_matches_manifest_text
   validate :ensure_storage_classes_desired_is_not_empty
   validate :ensure_storage_classes_contain_non_empty_strings
   validate :versioning_metadata_updates, on: :update
   validate :past_versions_cannot_be_updated, on: :update
+  validate :protected_managed_properties_updates, on: :update
   after_validation :set_file_count_and_total_size
   before_save :set_file_names
   around_update :manage_versioning, unless: :is_past_version?
 
   api_accessible :user, extend: :common do |t|
-    t.add :name
+    t.add lambda { |x| x.name || "" }, as: :name
     t.add :description
     t.add :properties
     t.add :portable_data_hash
@@ -75,6 +79,7 @@ class Collection < ArvadosModel
                 # correct timestamp in signed_manifest_text.
                 'manifest_text' => ['manifest_text', 'trash_at', 'is_trashed'],
                 'unsigned_manifest_text' => ['manifest_text'],
+                'name' => ['name'],
                 )
   end
 
@@ -193,6 +198,12 @@ class Collection < ArvadosModel
     end
   end
 
+  def name_null_if_empty
+    if name == ""
+      self.name = nil
+    end
+  end
+
   def set_file_names
     if self.manifest_text_changed?
       self.file_names = manifest_files
@@ -326,16 +337,16 @@ class Collection < ArvadosModel
   end
 
   def is_past_version?
-    if !new_record? && self.current_version_uuid_was != self.uuid_was
-      return true
-    else
-      return false
-    end
+    # Check for the '_was' values just in case the update operation
+    # includes a change on current_version_uuid or uuid.
+    !(new_record? || self.current_version_uuid_was == self.uuid_was)
   end
 
   def should_preserve_version?
     return false unless (Rails.configuration.Collections.CollectionVersioning && versionable_updates?(self.changes.keys))
 
+    return false if self.is_trashed
+
     idle_threshold = Rails.configuration.Collections.PreserveVersionIfIdle
     if !self.preserve_version_was &&
       (idle_threshold < 0 ||
@@ -511,7 +522,7 @@ class Collection < ArvadosModel
       joins("JOIN collections ON links.head_uuid = collections.uuid").
       order("links.created_at DESC")
 
-    docker_image_formats = Rails.configuration.Containers.SupportedDockerImageFormats
+    docker_image_formats = Rails.configuration.Containers.SupportedDockerImageFormats.keys.map(&:to_s)
 
     if (docker_image_formats.include? 'v1' and
         docker_image_formats.include? 'v2') or filter_compatible_format == false
@@ -606,6 +617,23 @@ class Collection < ArvadosModel
     self.storage_classes_confirmed ||= []
   end
 
+  # Sets managed properties at creation time
+  def managed_properties
+    managed_props = Rails.configuration.Collections.ManagedProperties.with_indifferent_access
+    if managed_props.empty?
+      return
+    end
+    (managed_props.keys - self.properties.keys).each do |key|
+      if managed_props[key]['Function'] == 'original_owner'
+        self.properties[key] = self.user_owner_uuid
+      elsif managed_props[key]['Value']
+        self.properties[key] = managed_props[key]['Value']
+      else
+        logger.warn "Unidentified default property definition '#{key}': #{managed_props[key].inspect}"
+      end
+    end
+  end
+
   def portable_manifest_text
     self.class.munge_manifest_locators(manifest_text) do |match|
       if match[2] # size
@@ -661,14 +689,31 @@ class Collection < ArvadosModel
   end
 
   def past_versions_cannot_be_updated
-    # We check for the '_was' values just in case the update operation
-    # includes a change on current_version_uuid or uuid.
     if is_past_version?
       errors.add(:base, "past versions cannot be updated")
       false
     end
   end
 
+  def protected_managed_properties_updates
+    managed_properties = Rails.configuration.Collections.ManagedProperties.with_indifferent_access
+    if managed_properties.empty? || !properties_changed? || current_user.is_admin
+      return true
+    end
+    protected_props = managed_properties.keys.select do |p|
+      Rails.configuration.Collections.ManagedProperties[p]['Protected']
+    end
+    # Pre-existent protected properties can't be updated
+    invalid_updates = properties_was.keys.select{|p| properties_was[p] != properties[p]} & protected_props
+    if !invalid_updates.empty?
+      invalid_updates.each do |p|
+        errors.add("protected property cannot be updated:", p)
+      end
+      raise PermissionDeniedError.new
+    end
+    true
+  end
+
   def versioning_metadata_updates
     valid = true
     if !is_past_version? && current_version_uuid_changed?