-require 'assign_uuid'
+require 'has_uuid'
+
class ArvadosModel < ActiveRecord::Base
self.abstract_class = true
before_save :ensure_ownership_path_leads_to_user
before_destroy :ensure_owner_uuid_is_permitted
before_destroy :ensure_permission_to_destroy
-
before_create :update_modified_by_fields
before_update :maybe_update_modified_by_fields
after_create :log_create
after_update :log_update
after_destroy :log_destroy
+ after_find :convert_serialized_symbols_to_strings
validate :ensure_serialized_attribute_type
validate :normalize_collection_uuids
validate :ensure_valid_uuids
self.columns.select { |col| col.name == attr.to_s }.first
end
+ # Return nil if current user is not allowed to see the list of
+ # writers. Otherwise, return a list of user_ and group_uuids with
+ # write permission. (If not returning nil, current_user is always in
+ # the list because can_manage permission is needed to see the list
+ # of writers.)
+ def writable_by
+ unless (owner_uuid == current_user.uuid or
+ current_user.is_admin or
+ current_user.groups_i_can(:manage).index(owner_uuid))
+ return nil
+ end
+ [owner_uuid, current_user.uuid] + permissions.collect do |p|
+ if ['can_write', 'can_manage'].index p.name
+ p.tail_uuid
+ end
+ end.compact.uniq
+ 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
end
def ensure_owner_uuid_is_permitted
- return false if !current_user
- self.owner_uuid ||= current_user.uuid
+ raise PermissionDeniedError if !current_user
+ if respond_to? :owner_uuid=
+ self.owner_uuid ||= current_user.uuid
+ end
if self.owner_uuid_changed?
- if current_user.uuid == self.owner_uuid or
+ if new_record?
+ return true
+ elsif current_user.uuid == self.owner_uuid or
current_user.can? write: self.owner_uuid
# current_user is, or has :write permission on, the new owner
else
logger.warn "User #{current_user.uuid} tried to change owner_uuid of #{self.class.to_s} #{self.uuid} to #{self.owner_uuid} but does not have permission to write to #{self.owner_uuid}"
- return false
+ raise PermissionDeniedError
end
end
if new_record?
def maybe_update_modified_by_fields
update_modified_by_fields if self.changed? or self.new_record?
+ true
end
def update_modified_by_fields
self.modified_at = Time.now
self.modified_by_user_uuid = current_user ? current_user.uuid : nil
self.modified_by_client_uuid = current_api_client ? current_api_client.uuid : nil
+ true
+ 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
+ false
+ elsif x.is_a? Array
+ x.each do |k|
+ return true if has_symbols?(k)
+ end
+ false
+ else
+ (x.class == Symbol)
+ end
+ 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
+ else
+ x
+ end
end
def ensure_serialized_attribute_type
# developer.
self.class.serialized_attributes.each do |colname, attr|
if attr.object_class
- unless self.attributes[colname].is_a? attr.object_class
- self.errors.add colname.to_sym, "must be a #{attr.object_class.to_s}"
+ if self.attributes[colname].class != attr.object_class
+ self.errors.add colname.to_sym, "must be a #{attr.object_class.to_s}, not a #{self.attributes[colname].class.to_s}"
+ elsif self.class.has_symbols? attributes[colname]
+ self.errors.add colname.to_sym, "must not contain symbols: #{attributes[colname].inspect}"
end
end
end
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]
+ self.send(colname + '=',
+ self.class.recursive_stringify(attributes[colname]))
+ end
+ end
+ end
+
def foreign_key_attributes
attributes.keys.select { |a| a.match /_uuid$/ }
end