8460: Merge branch 'master' into 8460-websocket-go
[arvados.git] / services / api / app / models / arvados_model.rb
index 5a6ce0af8147bfc015106cd9f28288ae1e4b6cb8..aed0309591e4ecbfa4c309747daee695417b16c8 100644 (file)
@@ -42,7 +42,13 @@ class ArvadosModel < ActiveRecord::Base
 
   class AlreadyLockedError < StandardError
     def http_status
-      403
+      422
+    end
+  end
+
+  class InvalidStateTransitionError < StandardError
+    def http_status
+      422
     end
   end
 
@@ -193,65 +199,47 @@ class ArvadosModel < ActiveRecord::Base
       return self
     end
 
-    # Collect the uuids for each user and any groups readable by each user.
+    # Collect the UUIDs of the authorized users.
     user_uuids = users_list.map { |u| u.uuid }
-    uuid_list = user_uuids + users_list.flat_map { |u| u.groups_i_can(:read) }
-    sql_conds = []
-    sql_params = []
-    sql_table = kwargs.fetch(:table_name, table_name)
-    or_object_uuid = ''
 
-    # This row is owned by a member of users_list, or owned by a group
-    # readable by a member of users_list
-    # or
-    # This row uuid is the uuid of a member of users_list
-    # or
-    # A permission link exists ('write' and 'manage' implicitly include
-    # 'read') from a member of users_list, or a group readable by users_list,
-    # to this row, or to the owner of this row (see join() below).
-    sql_conds += ["#{sql_table}.uuid in (?)"]
-    sql_params += [user_uuids]
+    # Collect the UUIDs of all groups readable by any of the
+    # authorized users. If one of these (or the UUID of one of the
+    # authorized users themselves) is an object's owner_uuid, that
+    # object is readable.
+    owner_uuids = user_uuids + users_list.flat_map { |u| u.groups_i_can(:read) }
+    owner_uuids.uniq!
 
-    if uuid_list.any?
-      sql_conds += ["#{sql_table}.owner_uuid in (?)"]
-      sql_params += [uuid_list]
+    sql_conds = []
+    sql_table = kwargs.fetch(:table_name, table_name)
 
-      sanitized_uuid_list = uuid_list.
-        collect { |uuid| sanitize(uuid) }.join(', ')
-      permitted_uuids = "(SELECT head_uuid FROM links WHERE link_class='permission' AND tail_uuid IN (#{sanitized_uuid_list}))"
-      sql_conds += ["#{sql_table}.uuid IN #{permitted_uuids}"]
-    end
+    # Match any object (evidently a group or user) whose UUID is
+    # listed explicitly in owner_uuids.
+    sql_conds += ["#{sql_table}.uuid in (:owner_uuids)"]
 
-    if sql_table == "links" and users_list.any?
-      # This row is a 'permission' or 'resources' link class
-      # The uuid for a member of users_list is referenced in either the head
-      # or tail of the link
-      sql_conds += ["(#{sql_table}.link_class in (#{sanitize 'permission'}, #{sanitize 'resources'}) AND (#{sql_table}.head_uuid IN (?) OR #{sql_table}.tail_uuid IN (?)))"]
-      sql_params += [user_uuids, user_uuids]
-    end
+    # Match any object whose owner is listed explicitly in
+    # owner_uuids.
+    sql_conds += ["#{sql_table}.owner_uuid IN (:owner_uuids)"]
 
-    if sql_table == "logs" and users_list.any?
-      # Link head points to the object described by this row
-      sql_conds += ["#{sql_table}.object_uuid IN #{permitted_uuids}"]
+    # Match the head of any permission link whose tail is listed
+    # explicitly in owner_uuids.
+    sql_conds += ["#{sql_table}.uuid IN (SELECT head_uuid FROM links WHERE link_class='permission' AND tail_uuid IN (:owner_uuids))"]
 
-      # This object described by this row is owned by this user, or owned by a group readable by this user
-      sql_conds += ["#{sql_table}.object_owner_uuid in (?)"]
-      sql_params += [uuid_list]
+    if sql_table == "links"
+      # Match any permission link that gives one of the authorized
+      # users some permission _or_ gives anyone else permission to
+      # view one of the authorized users.
+      sql_conds += ["(#{sql_table}.link_class in (:permission_link_classes) AND "+
+                    "(#{sql_table}.head_uuid IN (:user_uuids) OR #{sql_table}.tail_uuid IN (:user_uuids)))"]
     end
 
-    # Link head points to this row, or to the owner of this row (the
-    # thing to be read)
-    #
-    # Link tail originates from this user, or a group that is readable
-    # by this user (the identity with authorization to read)
-    #
-    # Link class is 'permission' ('write' and 'manage' implicitly
-    # include 'read')
-    where(sql_conds.join(' OR '), *sql_params)
+    where(sql_conds.join(' OR '),
+          owner_uuids: owner_uuids,
+          user_uuids: user_uuids,
+          permission_link_classes: ['permission', 'resources'])
   end
 
   def logged_attributes
-    attributes.except *Rails.configuration.unlogged_attributes
+    attributes.except(*Rails.configuration.unlogged_attributes)
   end
 
   def self.full_text_searchable_columns
@@ -283,6 +271,18 @@ class ArvadosModel < ActiveRecord::Base
 
   protected
 
+  def self.deep_sort_hash(x)
+    if x.is_a? Hash
+      x.sort.collect do |k, v|
+        [k, deep_sort_hash(v)]
+      end.to_h
+    elsif x.is_a? Array
+      x.collect { |v| deep_sort_hash(v) }
+    else
+      x
+    end
+  end
+
   def ensure_ownership_path_leads_to_user
     if new_record? or owner_uuid_changed?
       uuid_in_path = {owner_uuid => true, uuid => true}
@@ -490,7 +490,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def foreign_key_attributes
-    attributes.keys.select { |a| a.match /_uuid$/ }
+    attributes.keys.select { |a| a.match(/_uuid$/) }
   end
 
   def skip_uuid_read_permission_check
@@ -505,7 +505,7 @@ class ArvadosModel < ActiveRecord::Base
     foreign_key_attributes.each do |attr|
       attr_value = send attr
       if attr_value.is_a? String and
-          attr_value.match /^[0-9a-f]{32,}(\+[@\w]+)*$/
+          attr_value.match(/^[0-9a-f]{32,}(\+[@\w]+)*$/)
         begin
           send "#{attr}=", Collection.normalize_uuid(attr_value)
         rescue
@@ -530,7 +530,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def self.uuid_like_pattern
-    "_____-#{uuid_prefix}-_______________"
+    "#{Rails.configuration.uuid_prefix}-#{uuid_prefix}-_______________"
   end
 
   def self.uuid_regex
@@ -584,13 +584,12 @@ class ArvadosModel < ActiveRecord::Base
     unless uuid.is_a? String
       return nil
     end
-    resource_class = nil
 
     uuid.match HasUuid::UUID_REGEX do |re|
       return uuid_prefixes[re[1]] if uuid_prefixes[re[1]]
     end
 
-    if uuid.match /.+@.+/
+    if uuid.match(/.+@.+/)
       return Email
     end
 
@@ -636,7 +635,7 @@ class ArvadosModel < ActiveRecord::Base
   end
 
   def log_destroy
-    log_change('destroy') do |log|
+    log_change('delete') do |log|
       log.fill_properties('old', etag(@old_attributes), @old_logged_attributes)
       log.update_to nil
     end