class ApplicationController < ActionController::Base
include CurrentApiClient
include ThemesForRails::ActionController
+ include LoadParam
+ include RecordFilters
+ ERROR_ACTIONS = [:render_error, :render_not_found]
+
respond_to :json
protect_from_forgery
- around_filter :thread_with_auth_info, except: ERROR_ACTIONS
before_filter :respond_with_json_by_default
before_filter :remote_ip
- before_filter :require_auth_scope, :except => :render_not_found
- before_filter :catch_redirect_hint
+ before_filter :load_read_auths
+ before_filter :require_auth_scope, except: ERROR_ACTIONS
- before_filter :find_object_by_uuid, :except => [:index, :create,
- :render_error,
- :render_not_found]
+ before_filter :catch_redirect_hint
+ before_filter(:find_object_by_uuid,
+ except: [:index, :create] + ERROR_ACTIONS)
before_filter :load_limit_offset_order_params, only: [:index, :owned_items]
before_filter :load_where_param, only: [:index, :owned_items]
before_filter :load_filters_param, only: [:index, :owned_items]
protected
- def load_where_param
- if params[:where].nil? or params[:where] == ""
- @where = {}
- elsif params[:where].is_a? Hash
- @where = params[:where]
- elsif params[:where].is_a? String
- begin
- @where = Oj.load(params[:where])
- raise unless @where.is_a? Hash
- rescue
- raise ArgumentError.new("Could not parse \"where\" param as an object")
- end
- end
- @where = @where.with_indifferent_access
- end
-
- def load_filters_param
- @filters ||= []
- if params[:filters].is_a? Array
- @filters += params[:filters]
- elsif params[:filters].is_a? String and !params[:filters].empty?
- begin
- f = Oj.load params[:filters]
- raise unless f.is_a? Array
- @filters += f
- rescue
- raise ArgumentError.new("Could not parse \"filters\" param as an array")
- end
- end
- end
-
- def default_orders
- ["#{table_name}.modified_at desc"]
- end
-
- def load_limit_offset_order_params
- if params[:limit]
- unless params[:limit].to_s.match(/^\d+$/)
- raise ArgumentError.new("Invalid value for limit parameter")
- end
- @limit = params[:limit].to_i
- else
- @limit = DEFAULT_LIMIT
- end
-
- if params[:offset]
- unless params[:offset].to_s.match(/^\d+$/)
- raise ArgumentError.new("Invalid value for offset parameter")
- end
- @offset = params[:offset].to_i
- 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 = default_orders
- end
- end
-
def find_objects_for_index
- @objects ||= model_class.readable_by(current_user)
+ @objects ||= model_class.readable_by(*@read_users)
apply_where_limit_order_params
end
self.columns.select { |col| col.name == attr.to_s }.first
end
-- # def eager_load_associations
-- # self.class.columns.each do |col|
-- # re = col.name.match /^(.*)_kind$/
-- # if (re and
-- # self.respond_to? re[1].to_sym and
-- # (auuid = self.send((re[1] + '_uuid').to_sym)) and
-- # (aclass = self.class.kind_class(self.send(col.name.to_sym))) and
-- # (aobject = aclass.where('uuid=?', auuid).first))
-- # self.instance_variable_set('@'+re[1], aobject)
-- # end
-- # end
-- # end
--
- def self.readable_by user
- if user.is_admin
- # Admins can read anything, so return immediately.
- return self
- end
++ # Return a query with read permissions restricted to the union of of the
++ # permissions of the members of users_list, i.e. if something is readable by
++ # any user in users_list, it will be readable in the query returned by this
++ # function.
+ def self.readable_by(*users_list)
++ # Get rid of troublesome nils
+ users_list.compact!
- user_uuids = users_list.map { |u| u.uuid }
- uuid_list = user_uuids + users_list.flat_map { |u| u.groups_i_can(:read) }
- sanitized_uuid_list = uuid_list.
- collect { |uuid| sanitize(uuid) }.join(', ')
- sql_conds = []
- sql_params = []
++
++ # Check if any of the users are admin. If so, we're done.
+ if users_list.select { |u| u.is_admin }.empty?
++
++ # Collect the uuids for each user and any groups readable by each user.
++ user_uuids = users_list.map { |u| u.uuid }
++ uuid_list = user_uuids + users_list.flat_map { |u| u.groups_i_can(:read) }
++ sanitized_uuid_list = uuid_list.
++ collect { |uuid| sanitize(uuid) }.join(', ')
++ sql_conds = []
++ sql_params = []
++ or_object_uuid = ''
++
++ # This row is owned by a member of users_list, or owned by a group
++ # readable by a member of users_list
++ # or
++ # This row uuid is the uuid of a member of users_list
++ # or
++ # A permission link exists ('write' and 'manage' implicitly include
++ # 'read') from a member of users_list, or a group readable by users_list,
++ # to this row, or to the owner of this row (see join() below).
+ sql_conds += ["#{table_name}.owner_uuid in (?)",
+ "#{table_name}.uuid in (?)",
+ "permissions.head_uuid IS NOT NULL"]
+ sql_params += [uuid_list, user_uuids]
++
+ if self == Link and users_list.any?
++ # This row is a 'permission' or 'resources' link class
++ # The uuid for a member of users_list is referenced in either the head
++ # or tail of the link
+ sql_conds += ["(#{table_name}.link_class in (#{sanitize 'permission'}, #{sanitize 'resources'}) AND (#{table_name}.head_uuid IN (?) OR #{table_name}.tail_uuid IN (?)))"]
+ sql_params += [user_uuids, user_uuids]
+ end
+
- uuid_list = [user.uuid, *user.groups_i_can(:read)]
- sanitized_uuid_list = uuid_list.
- collect { |uuid| sanitize(uuid) }.join(', ')
- or_references_me = ''
++ if self == Log and users_list.any?
++ # Link head points to the object described by this row
++ or_object_uuid = ", #{table_name}.object_uuid"
+
- if self == User
- or_row_is_me = "OR (#{table_name}.uuid=#{sanitize user.uuid})"
- end
++ # This object described by this row is owned by this user, or owned by a group readable by this user
++ sql_conds += ["#{table_name}.object_owner_uuid in (?)"]
++ sql_params += [uuid_list]
++ end
+
- if self == Link
- 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
++ # Link head points to this row, or to the owner of this row (the thing to be read)
++ #
++ # Link tail originates from this user, or a group that is readable by this
++ # user (the identity with authorization to read)
++ #
++ # Link class is 'permission' ('write' and 'manage' implicitly include 'read')
++
++ joins("LEFT JOIN links permissions ON permissions.head_uuid in (#{table_name}.owner_uuid, #{table_name}.uuid #{or_object_uuid}) AND permissions.tail_uuid in (#{sanitized_uuid_list}) AND permissions.link_class='permission'")
++ .where(sql_conds.join(' OR '), *sql_params).uniq
+
- if self == Log
- or_object_uuid = ", #{table_name}.object_uuid"
- or_object_owner = "OR (#{table_name}.object_owner_uuid in (#{sanitized_uuid_list}))"
++ else
++ # At least one user is admin, so don't bother to apply any restrictions.
++ self
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(sql_conds.join(' OR '), *sql_params)
+
- # Link head points to this row, or to the owner of this row (the thing to be read)
- # (or the object described by this row, for logs table only)
- # Link tail originates from this user, or a group that is readable by this
- # user (the identity with authorization to read)
- # Link is any permission link ('write' and 'manage' implicitly include 'read')
- # The existence of such a link is tested in the where clause as permissions.head_uuid IS NOT NULL.
- # or
- # This row is owned by this user, or owned by a group readable by this user
- # or
- # This is the users table
- # 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 #{or_object_uuid}) AND permissions.tail_uuid in (#{sanitized_uuid_list}) AND permissions.link_class='permission'").
- where("permissions.head_uuid IS NOT NULL OR #{table_name}.owner_uuid in (?) #{or_row_is_me} #{or_references_me} #{or_object_owner}",
- uuid_list).uniq
end
def logged_attributes