2857: fix flaky time-sensitive tests
[arvados.git] / services / api / app / models / arvados_model.rb
index adff09d53c45de9ebf03593418723b822b7f81bc..95fd055d4939880967e79fdec0d982088e2f70aa 100644 (file)
@@ -20,6 +20,7 @@ class ArvadosModel < ActiveRecord::Base
   after_create :log_create
   after_update :log_update
   after_destroy :log_destroy
+  after_find :convert_serialized_symbols_to_strings
   validate :ensure_serialized_attribute_type
   validate :normalize_collection_uuids
   validate :ensure_valid_uuids
@@ -27,7 +28,7 @@ class ArvadosModel < ActiveRecord::Base
   # Note: This only returns permission links. It does not account for
   # permissions obtained via user.is_admin or
   # user.uuid==object.owner_uuid.
-  has_many :permissions, :foreign_key => :head_uuid, :class_name => 'Link', :primary_key => :uuid, :conditions => "link_class = 'permission'", dependent: :destroy
+  has_many :permissions, :foreign_key => :head_uuid, :class_name => 'Link', :primary_key => :uuid, :conditions => "link_class = 'permission'"
 
   class PermissionDeniedError < StandardError
     def http_status
@@ -197,10 +198,21 @@ class ArvadosModel < ActiveRecord::Base
           current_user.can? write: self.owner_uuid
         # current_user is, or has :write permission on, the new owner
       else
-        logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{self.uuid} but does not have permission to write #{self.owner_uuid_was}"
+        logger.warn "User #{current_user.uuid} tried to change owner_uuid of #{self.class.to_s} #{self.uuid} to #{self.owner_uuid} but does not have permission to write to #{self.owner_uuid}"
         raise PermissionDeniedError
       end
     end
+    if new_record?
+      return true
+    elsif current_user.uuid == self.owner_uuid_was or
+        current_user.uuid == self.uuid or
+        current_user.can? write: self.owner_uuid_was
+      # current user is, or has :write permission on, the previous owner
+      return true
+    else
+      logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{self.uuid} but does not have permission to write #{self.owner_uuid_was}"
+      raise PermissionDeniedError
+    end
   end
 
   def ensure_permission_to_save
@@ -252,6 +264,38 @@ class ArvadosModel < ActiveRecord::Base
     true
   end
 
+  def self.has_symbols? x
+    if x.is_a? Hash
+      x.each do |k,v|
+        return true if has_symbols?(k) or has_symbols?(v)
+      end
+      false
+    elsif x.is_a? Array
+      x.each do |k|
+        return true if has_symbols?(k)
+      end
+      false
+    else
+      (x.class == Symbol)
+    end
+  end
+
+  def self.recursive_stringify x
+    if x.is_a? Hash
+      Hash[x.collect do |k,v|
+             [recursive_stringify(k), recursive_stringify(v)]
+           end]
+    elsif x.is_a? Array
+      x.collect do |k|
+        recursive_stringify k
+      end
+    elsif x.is_a? Symbol
+      x.to_s
+    else
+      x
+    end
+  end
+
   def ensure_serialized_attribute_type
     # Specifying a type in the "serialize" declaration causes rails to
     # raise an exception if a different data type is retrieved from
@@ -261,13 +305,31 @@ class ArvadosModel < ActiveRecord::Base
     # developer.
     self.class.serialized_attributes.each do |colname, attr|
       if attr.object_class
-        unless self.attributes[colname].is_a? attr.object_class
-          self.errors.add colname.to_sym, "must be a #{attr.object_class.to_s}"
+        if self.attributes[colname].class != attr.object_class
+          self.errors.add colname.to_sym, "must be a #{attr.object_class.to_s}, not a #{self.attributes[colname].class.to_s}"
+        elsif self.class.has_symbols? attributes[colname]
+          self.errors.add colname.to_sym, "must not contain symbols: #{attributes[colname].inspect}"
         end
       end
     end
   end
 
+  def convert_serialized_symbols_to_strings
+    # ensure_serialized_attribute_type should prevent symbols from
+    # getting into the database in the first place. If someone managed
+    # to get them into the database (perhaps using an older version)
+    # we'll convert symbols to strings when loading from the
+    # database. (Otherwise, loading and saving an object with existing
+    # symbols in a serialized field will crash.)
+    self.class.serialized_attributes.each do |colname, attr|
+      if self.class.has_symbols? attributes[colname]
+        attributes[colname] = self.class.recursive_stringify attributes[colname]
+        self.send(colname + '=',
+                  self.class.recursive_stringify(attributes[colname]))
+      end
+    end
+  end
+
   def foreign_key_attributes
     attributes.keys.select { |a| a.match /_uuid$/ }
   end