19145: Make frozen projects non-writable by admins.
[arvados.git] / services / api / app / models / arvados_model.rb
index 3ddbafcdb234fc9afddb8392c0860135711b6ff3..07a31d81a8a129dc67acb4aa1a7fa8f39e253ea1 100644 (file)
@@ -220,7 +220,7 @@ class ArvadosModel < ApplicationRecord
   end
 
   def self.default_orders
-    ["#{table_name}.modified_at desc", "#{table_name}.uuid"]
+    ["#{table_name}.modified_at desc", "#{table_name}.uuid desc"]
   end
 
   def self.unique_columns
@@ -249,9 +249,9 @@ class ArvadosModel < ApplicationRecord
     # Return [] if this is a frozen project and the current user can't
     # unfreeze
     return [] if respond_to?(:frozen_by_uuid) && frozen_by_uuid &&
-                 !(Rails.configuration.API.UnfreezeProjectRequiresAdmin ?
-                     current_user.andand.is_admin :
-                     current_user.can?(manage: uuid))
+                 (Rails.configuration.API.UnfreezeProjectRequiresAdmin ?
+                    !current_user.andand.is_admin :
+                    !current_user.can?(manage: uuid))
     # Return [] if nobody can write because this object is inside a
     # frozen project
     return [] if FrozenGroup.where(uuid: owner_uuid).any?
@@ -302,6 +302,15 @@ class ArvadosModel < ApplicationRecord
     # For details on how the trashed_groups table is constructed, see
     # see db/migrate/20200501150153_permission_table.rb
 
+    # excluded_trash is a SQL expression that determines whether a row
+    # should be excluded from the results due to being trashed.
+    # Trashed items inside frozen projects are invisible to regular
+    # (non-admin) users even when using include_trash, so we have:
+    #
+    # (item_trashed || item_inside_trashed_project)
+    # &&
+    # (!caller_requests_include_trash ||
+    #  (item_inside_frozen_project && caller_is_not_admin))
     if (admin && include_trash) || sql_table == "api_client_authorizations"
       excluded_trash = "false"
     else
@@ -322,7 +331,7 @@ class ArvadosModel < ApplicationRecord
       # on trashed items.
       if !include_trash && sql_table != "api_client_authorizations"
         # Only include records where the owner is not trashed
-        sql_conds = "NOT #{excluded_trash}"
+        sql_conds = "NOT (#{excluded_trash})"
       end
     else
       # The core of the permission check is a join against the
@@ -422,7 +431,7 @@ class ArvadosModel < ApplicationRecord
                      "    WHERE user_uuid IN (#{user_uuids_subquery}) AND perm_level >= 3))) "
       end
 
-      sql_conds = "(#{owner_check} #{direct_check} #{links_cond}) AND NOT #{excluded_trash}"
+      sql_conds = "(#{owner_check} #{direct_check} #{links_cond}) AND NOT (#{excluded_trash})"
 
     end
 
@@ -647,7 +656,7 @@ class ArvadosModel < ApplicationRecord
       # itself. (If we're in the act of unfreezing, we only need
       # :unfreeze permission, which means "what write permission would
       # be if target weren't frozen")
-      unless ((respond_to?(:frozen_by_uuid) && frozen_by_uuid_in_database && !frozen_by_uuid) ?
+      unless ((respond_to?(:frozen_by_uuid) && frozen_by_uuid_was && !frozen_by_uuid) ?
                 current_user.can?(unfreeze: uuid) :
                 current_user.can?(write: uuid))
         logger.warn "User #{current_user.uuid} tried to modify #{self.class.to_s} #{self.uuid} without write permission"