Merge branch '18794-config-health'
[arvados.git] / services / api / app / controllers / arvados / v1 / links_controller.rb
index e20b8a41eae9614744d31424d681264f3e6d0fad..7716a3d5cffd9f6ac44d3bb691d06c98ddf7acf2 100644 (file)
@@ -49,17 +49,25 @@ class Arvados::V1::LinksController < ApplicationController
         .where(uuid: params[:uuid])
         .first
     elsif !current_user
-      @object = nil
+      super
     else
       # The usual permission-filtering index query is unnecessarily
-      # inefficient, and doesn't match all links that should be
-      # visible (see #18865).  Instead, we look up the link by UUID,
-      # then check whether (a) its tail_uuid is the current user or
-      # (b) its head_uuid is an object the current_user can_manage.
-      @object = Link.unscoped.where(uuid: params[:uuid]).first
-      if @object &&
-         current_user.uuid != @object.tail_uuid &&
-         !current_user.can?(manage: @object.head_uuid)
+      # inefficient, and doesn't match all permission links that
+      # should be visible (see #18865).  Instead, we look up the link
+      # by UUID, then check whether (a) its tail_uuid is the current
+      # user or (b) its head_uuid is an object the current_user
+      # can_manage.
+      link = Link.unscoped.where(uuid: params[:uuid]).first
+      if link && link.link_class != 'permission'
+        # Not a permission link. Re-fetch using generic
+        # permission-filtering query.
+        super
+      elsif link && (current_user.uuid == link.tail_uuid ||
+                     current_user.can?(manage: link.head_uuid))
+        # Permission granted.
+        @object = link
+      else
+        # Permission denied, i.e., link is invisible => 404.
         @object = nil
       end
     end
@@ -102,19 +110,28 @@ class Arvados::V1::LinksController < ApplicationController
       end
     end
 
-    # If filtering by one or more head_uuid, and the current user has
-    # manage permission on those uuids, bypass the normal readable_by
-    # query (which doesn't match all can_manage-able items, see
-    # #18865) -- just rely on the head_uuid filters.
-    @filters.map do |k|
-      if k[0] == 'head_uuid'
-        if k[1] == '=' && current_user.can?(manage: k[2])
+    # If the provided filters are enough to limit the results to
+    # permission links with specific head_uuids or
+    # tail_uuid=current_user, bypass the normal readable_by query
+    # (which doesn't match all can_manage-able items, see #18865) --
+    # just ensure the current user actually has can_manage permission
+    # for the provided head_uuids, removing any that don't. At that
+    # point the caller's filters are an effective permission filter.
+    if @filters.include?(['link_class', '=', 'permission'])
+      @filters.map do |k|
+        if k[0] == 'tail_uuid' && k[1] == '=' && k[2] == current_user.uuid
           @objects = Link.unscoped
-        elsif k[1] == 'in'
-          k[2].select! do |head_uuid|
-            current_user.can?(manage: head_uuid)
+        elsif k[0] == 'head_uuid'
+          if k[1] == '=' && current_user.can?(manage: k[2])
+            @objects = Link.unscoped
+          elsif k[1] == 'in'
+            # Modify the filter operand element (k[2]) in place,
+            # removing any non-permitted UUIDs.
+            k[2].select! do |head_uuid|
+              current_user.can?(manage: head_uuid)
+            end
+            @objects = Link.unscoped
           end
-          @objects = Link.unscoped
         end
       end
     end