[ARVADOS] updated: 923e2bd7d962f1a0feefc73ae4f4531c8235a591

git at public.curoverse.com git at public.curoverse.com
Wed Apr 23 10:57:34 EDT 2014


Summary of changes:
 .../api/app/controllers/application_controller.rb  |   97 ++++++++++++++++++
 services/api/app/models/arvados_model.rb           |   26 +++++-
 services/api/app/models/log.rb                     |    4 +-
 .../20140423132913_add_object_owner_to_logs.rb     |   21 ++++
 services/api/db/schema.rb                          |    3 +-
 services/api/lib/eventbus.rb                       |   74 +++++++++-----
 services/api/lib/record_filters.rb                 |  104 +------------------
 7 files changed, 199 insertions(+), 130 deletions(-)
 create mode 100644 services/api/db/migrate/20140423132913_add_object_owner_to_logs.rb

       via  923e2bd7d962f1a0feefc73ae4f4531c8235a591 (commit)
      from  04cc77648cc62c73433801475c27ede4ceb76c8b (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit 923e2bd7d962f1a0feefc73ae4f4531c8235a591
Author: Peter Amstutz <peter.amstutz at curoverse.com>
Date:   Wed Apr 23 10:57:31 2014 -0400

    - Added object_owner_uuid to logs table, which records the owner of the object
      being described at the time of the log entry.  Added migration.
    
    - Added special case permission checks for reading logs table, and added
      documentation comments on how permission checks work.
    
    - Eventbus reuses readable_by and record_filters in determining what to publish
      so provide consistent behavior between eventbus and regular API queries.
    
    - Put most of apply_where_limit_order_params back into application_controller,
      refactored record_filters into lib/
    
    - All of this still needs testing.

diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 9c5497b..663baa8 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -108,6 +108,103 @@ class ApplicationController < ActionController::Base
 
   protected
 
+  def apply_where_limit_order_params
+    ft = record_filters @filters
+    if ft[:cond_out].any?
+      @objects = @objects.where(ft[:cond_out].join(' AND '), *ft[:param_out])
+    end
+
+    if @where.is_a? Hash and @where.any?
+      conditions = ['1=1']
+      @where.each do |attr,value|
+        if attr.to_s == 'any'
+          if value.is_a?(Array) and
+              value.length == 2 and
+              value[0] == 'contains' then
+            ilikes = []
+            model_class.searchable_columns('ilike').each do |column|
+              ilikes << "#{table_name}.#{column} ilike ?"
+              conditions << "%#{value[1]}%"
+            end
+            if ilikes.any?
+              conditions[0] << ' and (' + ilikes.join(' or ') + ')'
+            end
+          end
+        elsif attr.to_s.match(/^[a-z][_a-z0-9]+$/) and
+            model_class.columns.collect(&:name).index(attr.to_s)
+          if value.nil?
+            conditions[0] << " and #{table_name}.#{attr} is ?"
+            conditions << nil
+          elsif value.is_a? Array
+            if value[0] == 'contains' and value.length == 2
+              conditions[0] << " and #{table_name}.#{attr} like ?"
+              conditions << "%#{value[1]}%"
+            else
+              conditions[0] << " and #{table_name}.#{attr} in (?)"
+              conditions << value
+            end
+          elsif value.is_a? String or value.is_a? Fixnum or value == true or value == false
+            conditions[0] << " and #{table_name}.#{attr}=?"
+            conditions << value
+          elsif value.is_a? Hash
+            # Not quite the same thing as "equal?" but better than nothing?
+            value.each do |k,v|
+              if v.is_a? String
+                conditions[0] << " and #{table_name}.#{attr} ilike ?"
+                conditions << "%#{k}%#{v}%"
+              end
+            end
+          end
+        end
+      end
+      if conditions.length > 1
+        conditions[0].sub!(/^1=1 and /, '')
+        @objects = @objects.
+          where(*conditions)
+      end
+    end
+
+    if params[:limit]
+      begin
+        @limit = params[:limit].to_i
+      rescue
+        raise ArgumentError.new("Invalid value for limit parameter")
+      end
+    else
+      @limit = 100
+    end
+    @objects = @objects.limit(@limit)
+
+    orders = []
+
+    if params[:offset]
+      begin
+        @objects = @objects.offset(params[:offset].to_i)
+        @offset = params[:offset].to_i
+      rescue
+        raise ArgumentError.new("Invalid value for limit parameter")
+      end
+    else
+      @offset = 0
+    end
+
+    orders = []
+    if params[:order]
+      params[:order].split(',').each do |order|
+        attr, direction = order.strip.split " "
+        direction ||= 'asc'
+        if attr.match /^[a-z][_a-z0-9]+$/ and
+            model_class.columns.collect(&:name).index(attr) and
+            ['asc','desc'].index direction.downcase
+          orders << "#{table_name}.#{attr} #{direction.downcase}"
+        end
+      end
+    end
+    if orders.empty?
+      orders << "#{table_name}.modified_at desc"
+    end
+    @objects = @objects.order(orders.join ", ")
+  end
 
   def find_objects_for_index
     @objects ||= model_class.readable_by(current_user)
diff --git a/services/api/app/models/arvados_model.rb b/services/api/app/models/arvados_model.rb
index 3a79810..449a061 100644
--- a/services/api/app/models/arvados_model.rb
+++ b/services/api/app/models/arvados_model.rb
@@ -79,11 +79,33 @@ class ArvadosModel < ActiveRecord::Base
     sanitized_uuid_list = uuid_list.
       collect { |uuid| sanitize(uuid) }.join(', ')
     or_references_me = ''
+
     if self == Link and user
       or_references_me = "OR (#{table_name}.link_class in (#{sanitize 'permission'}, #{sanitize 'resources'}) AND #{sanitize user.uuid} IN (#{table_name}.head_uuid, #{table_name}.tail_uuid))"
     end
-    joins("LEFT JOIN links permissions ON permissions.head_uuid in (#{table_name}.owner_uuid, #{table_name}.uuid) AND permissions.tail_uuid in (#{sanitized_uuid_list}) AND permissions.link_class='permission'").
-      where("?=? OR #{table_name}.owner_uuid in (?) OR #{table_name}.uuid=? OR permissions.head_uuid IS NOT NULL #{or_references_me}",
+
+    if self == Log and user
+      object_owner = ", #{table_name}.object_owner_uuid"
+      or_object_owner = "OR (#{table_name}.object_owner_uuid in (#{sanitized_uuid_list}))"
+    end
+
+    # Link head points to this row, or to the owner of this row
+    # (or owner of object described by this row, for logs table only)
+    # Link tail originates from this user, or a group that is readable by this user
+    # Link is any permission link ('write' and 'manage' implicitly grant 'read')
+    # or
+    # This row is owned by this user, or owned by a group readable by this user
+    # or
+    # This row uuid is equal this user uuid
+    # or
+    # This is the links table
+    # This row is a permission link
+    # The current user is referenced in either the head or tail of the link
+    # or
+    # This is the logs table
+    # This object described by this row is owned by this user, or owned by a group readable by this user
+    joins("LEFT JOIN links permissions ON permissions.head_uuid in (#{table_name}.owner_uuid, #{table_name}.uuid #{object_owner}) AND permissions.tail_uuid in (#{sanitized_uuid_list}) AND permissions.link_class='permission'").
+      where("?=? OR #{table_name}.owner_uuid in (?) OR #{table_name}.uuid=? OR permissions.head_uuid IS NOT NULL #{or_references_me} #{or_object_owner}",
             true, user.is_admin,
             uuid_list,
             user.uuid)
diff --git a/services/api/app/models/log.rb b/services/api/app/models/log.rb
index f8e337b..e7e5c1a 100644
--- a/services/api/app/models/log.rb
+++ b/services/api/app/models/log.rb
@@ -8,7 +8,7 @@ class Log < ArvadosModel
 
   api_accessible :user, extend: :common do |t|
     t.add :object_uuid
-    t.add :object, :if => :object
+    t.add :object_owner_uuid
     t.add :object_kind
     t.add :event_at
     t.add :event_type
@@ -24,6 +24,7 @@ class Log < ArvadosModel
 
   def fill_object(thing)
     self.object_uuid ||= thing.uuid
+    self.object_owner_uuid = thing.owner_uuid
     self.summary ||= "#{self.event_type} of #{thing.uuid}"
     self
   end
@@ -69,4 +70,5 @@ class Log < ArvadosModel
   def ensure_valid_uuids
     # logs can have references to deleted objects
   end
+
 end
diff --git a/services/api/db/migrate/20140423132913_add_object_owner_to_logs.rb b/services/api/db/migrate/20140423132913_add_object_owner_to_logs.rb
new file mode 100644
index 0000000..7fa4702
--- /dev/null
+++ b/services/api/db/migrate/20140423132913_add_object_owner_to_logs.rb
@@ -0,0 +1,21 @@
+class AddObjectOwnerToLogs < ActiveRecord::Migration
+  include CurrentApiClient
+
+  def up
+    add_column :logs, :object_owner_uuid, :string
+    act_as_system_user do
+      Log.all.each do |log|
+        if log.properties[:new_attributes]
+          log.object_owner_uuid = log.properties[:new_attributes][:owner_uuid]
+        elsif log.properties[:old_attributes]
+          log.object_owner_uuid = log.properties[:old_attributes][:owner_uuid]
+        end
+        log.save!
+      end
+    end
+  end
+
+  def down
+    remove_column :logs, :object_owner_uuid
+  end
+end
diff --git a/services/api/db/schema.rb b/services/api/db/schema.rb
index e2301e5..fc8e436 100644
--- a/services/api/db/schema.rb
+++ b/services/api/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20140407184311) do
+ActiveRecord::Schema.define(:version => 20140423132913) do
 
   create_table "api_client_authorizations", :force => true do |t|
     t.string   "api_token",                                           :null => false
@@ -268,6 +268,7 @@ ActiveRecord::Schema.define(:version => 20140407184311) do
     t.datetime "created_at",              :null => false
     t.datetime "updated_at",              :null => false
     t.datetime "modified_at"
+    t.string   "object_owner_uuid"
   end
 
   add_index "logs", ["created_at"], :name => "index_logs_on_created_at"
diff --git a/services/api/lib/eventbus.rb b/services/api/lib/eventbus.rb
index 646ee83..516f5fa 100644
--- a/services/api/lib/eventbus.rb
+++ b/services/api/lib/eventbus.rb
@@ -2,6 +2,7 @@ require 'eventmachine'
 require 'oj'
 require 'faye/websocket'
 require 'record_filters'
+require 'load_param'
 
 module Faye
   class WebSocket
@@ -28,27 +29,9 @@ class Filter
   end
 end
 
-class FilterController
-  include RecordFilters
-
-  def initialize(f, o)
-    @filters = f
-    @objects = o
-    apply_where_limit_order_params
-  end
-
-  def each &b
-    @objects.each &b
-  end
-
-  def params
-    {}
-  end
-
-end
-
 class EventBus
   include CurrentApiClient
+  include RecordFilters
 
   def initialize
     @channel = EventMachine::Channel.new
@@ -65,23 +48,58 @@ class EventBus
 
     ws.user = current_user
     ws.filters = []
+    ws.last_log_id = nil
 
     sub = @channel.subscribe do |msg|
-      Log.where(id: msg.to_i).each do |l|
+      # Must have at least one filter set up to receive events
+      if ws.filters.length > 0
+
+        # Start with log rows readable by user, sorted in ascending order
+        logs = Log.readable_by(ws.user).order("id asc")
+
+        if ws.last_log_id
+          # Only get log rows that are new
+          logs = logs.where("log.id > ? and log.id <= ?", ws.last_log_id, msg.to_i)
+        else
+          # No last log id, so only look at the most recently changed row
+          logs = logs.where("log.id = ?", msg.to_i)
+        end
+
+        # Record the most recent row
         ws.last_log_id = msg.to_i
-        if rsc = ArvadosModel::resource_class_for_uuid(l.object_uuid)
-          permitted = rsc.readable_by(ws.user).where(uuid: l.object_uuid)
-          ws.filters.each do |filter|
-            FilterController.new(filter, permitted).each do
-              ws.send(l.as_api_response.to_json)
-            end
-          end
+
+        # Now process filters provided by client
+        cond_out = []
+        param_out = []
+        ws.filters.each do |filter|
+          ft = record_filters filter.filters
+          cond_out += ft[:cond_out]
+          param_out += ft[:param_out]
         end
+
+        # Add filters to query
+        if cond_out.any?
+          logs = logs.where(cond_out.join(' OR '), *param_out)
+        end
+
+        # Finally execute query and send matching rows
+        logs.each do |l|
+          ws.send(l.as_api_response.to_json)
+        end
+      else
+        # No filters set up, so just record the sequence number
+        ws.last_log_id.nil = msg.to_i
       end
     end
 
     ws.on :message do |event|
-      ws.filters = Filter.new oj.parse(event.data)
+      p = oj.parse(event.data)
+      if p[:method] == 'subscribe'
+        if p[:starting_log_id]
+          ws.last_log_id = p[:starting_log_id].to_i
+        end
+        ws.filters.push(Filter.new p)
+      end
     end
 
     ws.on :close do |event|
diff --git a/services/api/lib/record_filters.rb b/services/api/lib/record_filters.rb
index fab16bb..6750797 100644
--- a/services/api/lib/record_filters.rb
+++ b/services/api/lib/record_filters.rb
@@ -6,11 +6,11 @@
 #   @objects
 module RecordFilters
 
-  def apply_where_limit_order_params
-    if @filters.is_a? Array and @filters.any?
-      cond_out = []
-      param_out = []
-      @filters.each do |attr, operator, operand|
+  def record_filters filters
+    cond_out = []
+    param_out = []
+    if filters.is_a? Array and filters.any?
+      filters.each do |attr, operator, operand|
         if !model_class.searchable_columns(operator).index attr.to_s
           raise ArgumentError.new("Invalid attribute '#{attr}' in condition")
         end
@@ -46,100 +46,8 @@ module RecordFilters
           cond_out << cond.join(' OR ')
         end
       end
-      if cond_out.any?
-        @objects = @objects.where(cond_out.join(' AND '), *param_out)
-      end
-    end
-    if @where.is_a? Hash and @where.any?
-      conditions = ['1=1']
-      @where.each do |attr,value|
-        if attr.to_s == 'any'
-          if value.is_a?(Array) and
-              value.length == 2 and
-              value[0] == 'contains' then
-            ilikes = []
-            model_class.searchable_columns('ilike').each do |column|
-              ilikes << "#{table_name}.#{column} ilike ?"
-              conditions << "%#{value[1]}%"
-            end
-            if ilikes.any?
-              conditions[0] << ' and (' + ilikes.join(' or ') + ')'
-            end
-          end
-        elsif attr.to_s.match(/^[a-z][_a-z0-9]+$/) and
-            model_class.columns.collect(&:name).index(attr.to_s)
-          if value.nil?
-            conditions[0] << " and #{table_name}.#{attr} is ?"
-            conditions << nil
-          elsif value.is_a? Array
-            if value[0] == 'contains' and value.length == 2
-              conditions[0] << " and #{table_name}.#{attr} like ?"
-              conditions << "%#{value[1]}%"
-            else
-              conditions[0] << " and #{table_name}.#{attr} in (?)"
-              conditions << value
-            end
-          elsif value.is_a? String or value.is_a? Fixnum or value == true or value == false
-            conditions[0] << " and #{table_name}.#{attr}=?"
-            conditions << value
-          elsif value.is_a? Hash
-            # Not quite the same thing as "equal?" but better than nothing?
-            value.each do |k,v|
-              if v.is_a? String
-                conditions[0] << " and #{table_name}.#{attr} ilike ?"
-                conditions << "%#{k}%#{v}%"
-              end
-            end
-          end
-        end
-      end
-      if conditions.length > 1
-        conditions[0].sub!(/^1=1 and /, '')
-        @objects = @objects.
-          where(*conditions)
-      end
-    end
-
-    if params[:limit]
-      begin
-        @limit = params[:limit].to_i
-      rescue
-        raise ArgumentError.new("Invalid value for limit parameter")
-      end
-    else
-      @limit = 100
-    end
-    @objects = @objects.limit(@limit)
-
-    orders = []
-
-    if params[:offset]
-      begin
-        @objects = @objects.offset(params[:offset].to_i)
-        @offset = params[:offset].to_i
-      rescue
-        raise ArgumentError.new("Invalid value for limit parameter")
-      end
-    else
-      @offset = 0
-    end
-
-    orders = []
-    if params[:order]
-      params[:order].split(',').each do |order|
-        attr, direction = order.strip.split " "
-        direction ||= 'asc'
-        if attr.match /^[a-z][_a-z0-9]+$/ and
-            model_class.columns.collect(&:name).index(attr) and
-            ['asc','desc'].index direction.downcase
-          orders << "#{table_name}.#{attr} #{direction.downcase}"
-        end
-      end
-    end
-    if orders.empty?
-      orders << "#{table_name}.modified_at desc"
     end
-    @objects = @objects.order(orders.join ", ")
+    {:cond_out => cond_out, :param_out => param_out}
   end
 
 end

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list