require 'serializers'
require 'request_error'
-class ArvadosModel < ActiveRecord::Base
+class ArvadosModel < ApplicationRecord
self.abstract_class = true
include ArvadosModelUpdates
after_create :log_create
after_update :log_update
after_destroy :log_destroy
- after_find :convert_serialized_symbols_to_strings
before_validation :normalize_collection_uuids
before_validation :set_default_owner
validate :ensure_valid_uuids
class_name: 'Link',
primary_key: :uuid)
+ # If async is true at create or update, permission graph
+ # update is deferred allowing making multiple calls without the performance
+ # penalty.
+ attr_accessor :async_permissions_update
+
+ # Ignore listed attributes on mass assignments
+ def self.protected_attributes
+ []
+ end
+
class PermissionDeniedError < RequestError
def http_status
403
# The following permit! is necessary even with
# "ActionController::Parameters.permit_all_parameters = true",
# because permit_all does not permit nested attributes.
+ raw_params ||= {}
+
if raw_params
+ raw_params = raw_params.to_hash
+ raw_params.delete_if { |k, _| self.protected_attributes.include? k }
serialized_attributes.each do |colname, coder|
param = raw_params[colname.to_sym]
if param.nil?
raise ArgumentError.new("#{colname} parameter cannot have non-string hash keys")
end
end
+ # Check JSONB columns that aren't listed on serialized_attributes
+ columns.select{|c| c.type == :jsonb}.collect{|j| j.name}.each do |colname|
+ if serialized_attributes.include? colname || raw_params[colname.to_sym].nil?
+ next
+ end
+ if has_nonstring_keys?(raw_params[colname.to_sym])
+ raise ArgumentError.new("#{colname} parameter cannot have non-string hash keys")
+ end
+ end
end
ActionController::Parameters.new(raw_params).permit!
end
if !include_trash
if sql_table != "api_client_authorizations"
# Only include records where the owner is not trashed
- sql_conds = "NOT EXISTS(SELECT 1 FROM #{PERMISSION_VIEW} "+
- "WHERE trashed = 1 AND "+
- "(#{sql_table}.owner_uuid = target_uuid)) #{exclude_trashed_records}"
+ sql_conds = "#{sql_table}.owner_uuid NOT IN (SELECT target_uuid FROM #{PERMISSION_VIEW} "+
+ "WHERE trashed = 1) #{exclude_trashed_records}"
end
end
else
# see issue 13208 for details.
# Match a direct read permission link from the user to the record uuid
- direct_check = "EXISTS(SELECT 1 FROM #{PERMISSION_VIEW} "+
- "WHERE user_uuid IN (:user_uuids) AND perm_level >= 1 #{trashed_check} AND target_uuid = #{sql_table}.uuid)"
+ direct_check = "#{sql_table}.uuid IN (SELECT target_uuid FROM #{PERMISSION_VIEW} "+
+ "WHERE user_uuid IN (:user_uuids) AND perm_level >= 1 #{trashed_check})"
# Match a read permission link from the user to the record's owner_uuid
owner_check = ""
if sql_table != "api_client_authorizations" and sql_table != "groups" then
- owner_check = "OR EXISTS(SELECT 1 FROM #{PERMISSION_VIEW} "+
- "WHERE user_uuid IN (:user_uuids) AND perm_level >= 1 #{trashed_check} AND target_uuid = #{sql_table}.owner_uuid AND target_owner_uuid IS NOT NULL) "
+ owner_check = "OR #{sql_table}.owner_uuid IN (SELECT target_uuid FROM #{PERMISSION_VIEW} "+
+ "WHERE user_uuid IN (:user_uuids) AND perm_level >= 1 #{trashed_check} AND target_owner_uuid IS NOT NULL) "
end
links_cond = ""
# discover a unique name. It is necessary to handle name choosing at
# this level (as opposed to the client) to ensure that record creation
# never fails due to a race condition.
- err = rn.original_exception
+ err = rn.cause
raise unless err.is_a?(PG::UniqueViolation)
# Unfortunately ActiveRecord doesn't abstract out any of the
end
end
+ def user_owner_uuid
+ if self.owner_uuid.nil?
+ return current_user.uuid
+ end
+ owner_class = ArvadosModel.resource_class_for_uuid(self.owner_uuid)
+ if owner_class == User
+ self.owner_uuid
+ else
+ owner_class.find_by_uuid(self.owner_uuid).user_owner_uuid
+ end
+ end
+
def logged_attributes
- attributes.except(*Rails.configuration.unlogged_attributes)
+ attributes.except(*Rails.configuration.AuditLogs.UnloggedAttributes.keys)
end
def self.full_text_searchable_columns
end.map(&:name)
end
+ def self.full_text_coalesce
+ full_text_searchable_columns.collect do |column|
+ is_jsonb = self.columns.select{|x|x.name == column}[0].type == :jsonb
+ cast = (is_jsonb || serialized_attributes[column]) ? '::text' : ''
+ "coalesce(#{column}#{cast},'')"
+ end
+ end
+
+ def self.full_text_trgm
+ "(#{full_text_coalesce.join(" || ' ' || ")})"
+ end
+
def self.full_text_tsvector
parts = full_text_searchable_columns.collect do |column|
- cast = serialized_attributes[column] ? '::text' : ''
+ is_jsonb = self.columns.select{|x|x.name == column}[0].type == :jsonb
+ cast = (is_jsonb || serialized_attributes[column]) ? '::text' : ''
"coalesce(#{column}#{cast},'')"
end
- "to_tsvector('english', #{parts.join(" || ' ' || ")})"
+ "to_tsvector('english', substr(#{parts.join(" || ' ' || ")}, 0, 8000))"
end
def self.apply_filters query, filters
end
rescue ActiveRecord::RecordNotFound => e
errors.add :owner_uuid, "is not owned by any user: #{e}"
- return false
+ throw(:abort)
end
if uuid_in_path[x]
if x == owner_uuid
else
errors.add :owner_uuid, "has an ownership cycle"
end
- return false
+ throw(:abort)
end
uuid_in_path[x] = true
end
self.owner_uuid ||= current_default_owner if self.respond_to? :owner_uuid=
if !anonymous_updater
self.modified_by_user_uuid = current_user ? current_user.uuid : nil
+ end
+ if !timeless_updater
self.modified_at = current_time
end
self.modified_by_client_uuid = current_api_client ? current_api_client.uuid : nil
false
end
- def self.has_symbols? x
- if x.is_a? Hash
- x.each do |k,v|
- return true if has_symbols?(k) or has_symbols?(v)
- end
- elsif x.is_a? Array
- x.each do |k|
- return true if has_symbols?(k)
- end
- elsif x.is_a? Symbol
- return true
- elsif x.is_a? String
- return true if x.start_with?(':') && !x.start_with?('::')
- end
- false
- end
-
- def self.recursive_stringify x
- if x.is_a? Hash
- Hash[x.collect do |k,v|
- [recursive_stringify(k), recursive_stringify(v)]
- end]
- elsif x.is_a? Array
- x.collect do |k|
- recursive_stringify k
- end
- elsif x.is_a? Symbol
- x.to_s
- elsif x.is_a? String and x.start_with?(':') and !x.start_with?('::')
- x[1..-1]
- else
- x
- end
- end
-
def self.where_serialized(colname, value, md5: false)
colsql = colname.to_s
if md5
self.class.serialized_attributes
end
- def convert_serialized_symbols_to_strings
- # ensure_serialized_attribute_type should prevent symbols from
- # getting into the database in the first place. If someone managed
- # to get them into the database (perhaps using an older version)
- # we'll convert symbols to strings when loading from the
- # database. (Otherwise, loading and saving an object with existing
- # symbols in a serialized field will crash.)
- self.class.serialized_attributes.each do |colname, attr|
- if self.class.has_symbols? attributes[colname]
- attributes[colname] = self.class.recursive_stringify attributes[colname]
- send(colname + '=',
- self.class.recursive_stringify(attributes[colname]))
- end
- end
- end
-
def foreign_key_attributes
attributes.keys.select { |a| a.match(/_uuid$/) }
end
end
def self.uuid_like_pattern
- "#{Rails.configuration.uuid_prefix}-#{uuid_prefix}-_______________"
+ "#{Rails.configuration.ClusterID}-#{uuid_prefix}-_______________"
end
def self.uuid_regex
end
def is_audit_logging_enabled?
- return !(Rails.configuration.max_audit_log_age.to_i == 0 &&
- Rails.configuration.max_audit_log_delete_batch.to_i > 0)
+ return !(Rails.configuration.AuditLogs.MaxAge.to_i == 0 &&
+ Rails.configuration.AuditLogs.MaxDeleteBatch.to_i > 0)
end
def log_start_state