16007: Handle overlapping permissions correctly
[arvados.git] / services / api / app / models / link.rb
index d9b8f6f09b191230b6635fd8cd0b1cadf9ccf5ab..21d89767c7139a0c8b7ae8eb67595d6ebe336110 100644 (file)
@@ -1,15 +1,23 @@
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: AGPL-3.0
+
 class Link < ArvadosModel
   include HasUuid
   include KindAndEtag
   include CommonApiTemplate
-  serialize :properties, Hash
+
+  # Posgresql JSONB columns should NOT be declared as serialized, Rails 5
+  # already know how to properly treat them.
+  attribute :properties, :jsonbHash, default: {}
+
+  validate :name_links_are_obsolete
   before_create :permission_to_attach_to_objects
   before_update :permission_to_attach_to_objects
-  after_update :maybe_invalidate_permissions_cache
-  after_create :maybe_invalidate_permissions_cache
-  after_destroy :maybe_invalidate_permissions_cache
-  attr_accessor :head_kind, :tail_kind
-  validate :name_links_are_obsolete
+  after_update :call_update_permissions
+  after_create :call_update_permissions
+  before_destroy :clear_permissions
+  after_destroy :check_permissions
 
   api_accessible :user, extend: :common do |t|
     t.add :tail_uuid
@@ -21,11 +29,6 @@ class Link < ArvadosModel
     t.add :properties
   end
 
-  def properties
-    @properties ||= Hash.new
-    super
-  end
-
   def head_kind
     if k = ArvadosModel::resource_class_for_uuid(head_uuid)
       k.kind
@@ -50,23 +53,40 @@ class Link < ArvadosModel
     # Administrators can grant permissions
     return true if current_user.is_admin
 
-    # All users can grant permissions on objects they own or can manage
     head_obj = ArvadosModel.find_by_uuid(head_uuid)
+
+    # No permission links can be pointed to past collection versions
+    return false if head_obj.is_a?(Collection) && head_obj.current_version_uuid != head_uuid
+
+    # All users can grant permissions on objects they own or can manage
     return true if current_user.can?(manage: head_obj)
 
     # Default = deny.
     false
   end
 
-  def maybe_invalidate_permissions_cache
+  PERM_LEVEL = {
+    'can_read' => 1,
+    'can_login' => 1,
+    'can_write' => 2,
+    'can_manage' => 3,
+  }
+
+  def call_update_permissions
+    if self.link_class == 'permission'
+      update_permissions tail_uuid, head_uuid, PERM_LEVEL[name], self.uuid
+    end
+  end
+
+  def clear_permissions
     if self.link_class == 'permission'
-      # Clearing the entire permissions cache can generate many
-      # unnecessary queries if many active users are not affected by
-      # this change. In such cases it would be better to search cached
-      # permissions for head_uuid and tail_uuid, and invalidate the
-      # cache for only those users. (This would require a browseable
-      # cache.)
-      User.invalidate_permissions_cache
+      update_permissions tail_uuid, head_uuid, REVOKE_PERM, self.uuid
+    end
+  end
+
+  def check_permissions
+    if self.link_class == 'permission'
+      check_permissions_against_full_refresh
     end
   end
 
@@ -96,5 +116,4 @@ class Link < ArvadosModel
       super
     end
   end
-
 end