Merge branch 'master' into 3036-collection-uuids
[arvados.git] / services / api / app / models / arvados_model.rb
index 1ea9332484e793f3d069a03fd8e0cc4605e59dd5..f95a35e739d470dfa57e0d753ef1ea864ac9a193 100644 (file)
@@ -21,8 +21,8 @@ class ArvadosModel < ActiveRecord::Base
   after_update :log_update
   after_destroy :log_destroy
   after_find :convert_serialized_symbols_to_strings
+  before_validation :normalize_collection_uuids
   validate :ensure_serialized_attribute_type
-  validate :normalize_collection_uuids
   validate :ensure_valid_uuids
 
   # Note: This only returns permission links. It does not account for
@@ -68,6 +68,28 @@ class ArvadosModel < ActiveRecord::Base
     self.columns.select { |col| col.name == attr.to_s }.first
   end
 
+  def self.attributes_required_columns
+    # This method returns a hash.  Each key is the name of an API attribute,
+    # and it's mapped to a list of database columns that must be fetched
+    # to generate that attribute.
+    # This implementation generates a simple map of attributes to
+    # matching column names.  Subclasses can override this method
+    # to specify that method-backed API attributes need to fetch
+    # specific columns from the database.
+    all_columns = columns.map(&:name)
+    api_column_map = Hash.new { |hash, key| hash[key] = [] }
+    methods.grep(/^api_accessible_\w+$/).each do |method_name|
+      next if method_name == :api_accessible_attributes
+      send(method_name).each_pair do |api_attr_name, col_name|
+        col_name = col_name.to_s
+        if all_columns.include?(col_name)
+          api_column_map[api_attr_name.to_s] |= [col_name]
+        end
+      end
+    end
+    api_column_map
+  end
+
   # Return nil if current user is not allowed to see the list of
   # writers. Otherwise, return a list of user_ and group_uuids with
   # write permission. (If not returning nil, current_user is always in
@@ -176,7 +198,7 @@ class ArvadosModel < ActiveRecord::Base
     if new_record? or owner_uuid_changed?
       uuid_in_path = {owner_uuid => true, uuid => true}
       x = owner_uuid
-      while (owner_class = self.class.resource_class_for_uuid(x)) != User
+      while (owner_class = ArvadosModel::resource_class_for_uuid(x)) != User
         begin
           if x == uuid
             # Test for cycles with the new version, not the DB contents
@@ -206,31 +228,50 @@ class ArvadosModel < ActiveRecord::Base
 
   def ensure_owner_uuid_is_permitted
     raise PermissionDeniedError if !current_user
-    if respond_to? :owner_uuid=
+
+    if new_record? and respond_to? :owner_uuid=
       self.owner_uuid ||= current_user.uuid
     end
-    if self.owner_uuid_changed?
-      if new_record?
-        return true
-      elsif current_user.uuid == self.owner_uuid or
-          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 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
+
+    if self.owner_uuid.nil?
+      errors.add :owner_uuid, "cannot be nil"
+      raise PermissionDeniedError
+    end
+
+    rsc_class = ArvadosModel::resource_class_for_uuid owner_uuid
+    unless rsc_class == User or rsc_class == Group
+      errors.add :owner_uuid, "can only be set to User or Group"
+      raise PermissionDeniedError
     end
-    if new_record?
-      return true
-    elsif current_user.uuid == self.owner_uuid_was or
+
+    # Verify "write" permission on old owner
+    # default fail unless one of:
+    # owner_uuid did not change
+    # previous owner_uuid is nil
+    # current user is the old owner
+    # current user is this object
+    # current user can_write old owner
+    unless !owner_uuid_changed? or
+        owner_uuid_was.nil? or
+        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}"
+      logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{uuid} but does not have permission to write old owner_uuid #{owner_uuid_was}"
+      errors.add :owner_uuid, "cannot be changed without write permission on old owner"
       raise PermissionDeniedError
     end
+
+    # Verify "write" permission on new owner
+    # default fail unless one of:
+    # current_user is this object
+    # current user can_write new owner
+    unless current_user == self or current_user.can? write: owner_uuid
+      logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{uuid} but does not have permission to write new owner_uuid #{owner_uuid}"
+      errors.add :owner_uuid, "cannot be changed without write permission on new owner"
+      raise PermissionDeniedError
+    end
+
+    true
   end
 
   def ensure_permission_to_save
@@ -374,8 +415,6 @@ class ArvadosModel < ActiveRecord::Base
     end
   end
 
-  @@UUID_REGEX = /^[0-9a-z]{5}-([0-9a-z]{5})-[0-9a-z]{15}$/
-
   @@prefixes_hash = nil
   def self.uuid_prefixes
     unless @@prefixes_hash
@@ -394,7 +433,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def ensure_valid_uuids
-    specials = [system_user_uuid, 'd41d8cd98f00b204e9800998ecf8427e+0']
+    specials = [system_user_uuid]
 
     foreign_key_attributes.each do |attr|
       if new_record? or send (attr + "_changed?")
@@ -440,13 +479,10 @@ class ArvadosModel < ActiveRecord::Base
     unless uuid.is_a? String
       return nil
     end
-    if uuid.match /^[0-9a-f]{32}(\+[^,]+)*(,[0-9a-f]{32}(\+[^,]+)*)*$/
-      return Collection
-    end
     resource_class = nil
 
     Rails.application.eager_load!
-    uuid.match @@UUID_REGEX do |re|
+    uuid.match HasUuid::UUID_REGEX do |re|
       return uuid_prefixes[re[1]] if uuid_prefixes[re[1]]
     end