Merge branch '21600-banner-tests'
[arvados.git] / services / api / app / controllers / arvados / v1 / links_controller.rb
index 64e070999aa8cbfc7d093c6aa20f8d444492dd61..c956bfc9b467b5df3a98ef334961d4eb465bc3fc 100644 (file)
@@ -20,6 +20,42 @@ class Arvados::V1::LinksController < ApplicationController
 
     resource_attrs.delete :head_kind
     resource_attrs.delete :tail_kind
+
+    if resource_attrs[:link_class] == 'permission' && Link::PermLevel[resource_attrs[:name]]
+      existing = Link.
+                   lock. # select ... for update
+                   where(link_class: 'permission',
+                         tail_uuid: resource_attrs[:tail_uuid],
+                         head_uuid: resource_attrs[:head_uuid],
+                         name: Link::PermLevel.keys).first
+      if existing
+        @object = existing
+        if Link::PermLevel[resource_attrs[:name]] > Link::PermLevel[existing.name]
+          # upgrade existing permission link to the requested level.
+          return update
+        else
+          # no-op: existing permission is already greater or equal to
+          # the newly requested permission.
+          return show
+        end
+      end
+    elsif resource_attrs[:link_class] == 'permission' &&
+          resource_attrs[:name] == 'can_login' &&
+          resource_attrs[:properties].respond_to?(:has_key?) &&
+          resource_attrs[:properties].has_key?(:username)
+      existing = Link.
+                   lock. # select ... for update
+                   where(link_class: 'permission',
+                         tail_uuid: resource_attrs[:tail_uuid],
+                         head_uuid: resource_attrs[:head_uuid]).
+                   where('properties @> ?', SafeJSON.dump({'username' => resource_attrs[:properties][:username]})).
+                   first
+      if existing
+        @object = existing
+        return show
+      end
+    end
+
     super
   end
 
@@ -38,7 +74,7 @@ class Arvados::V1::LinksController < ApplicationController
 
   protected
 
-  def find_object_by_uuid
+  def find_object_by_uuid(with_lock: false)
     if params[:id] && params[:id].match(/\D/)
       params[:uuid] = params.delete :id
     end
@@ -49,7 +85,7 @@ class Arvados::V1::LinksController < ApplicationController
         .where(uuid: params[:uuid])
         .first
     elsif !current_user
-      super
+      super(with_lock: with_lock)
     else
       # The usual permission-filtering index query is unnecessarily
       # inefficient, and doesn't match all permission links that
@@ -57,14 +93,21 @@ class Arvados::V1::LinksController < ApplicationController
       # 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 && @object.link_class != 'permission'
-        # Throw this out and re-fetch using generic permission query
-        @object = nil
-        super
-      elsif @object &&
-         current_user.uuid != @object.tail_uuid &&
-         !current_user.can?(manage: @object.head_uuid)
+      model = Link
+      if with_lock && Rails.configuration.API.LockBeforeUpdate
+        model = model.lock
+      end
+      link = model.unscoped.where(uuid: params[:uuid]).first
+      if link && link.link_class != 'permission'
+        # Not a permission link. Re-fetch using generic
+        # permission-filtering query.
+        super(with_lock: with_lock)
+      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
@@ -122,6 +165,8 @@ class Arvados::V1::LinksController < ApplicationController
           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