1 class Link < ArvadosModel
4 include CommonApiTemplate
5 serialize :properties, Hash
6 before_create :permission_to_attach_to_objects
7 before_update :permission_to_attach_to_objects
8 before_create :assign_generic_name_if_none_given
9 after_update :maybe_invalidate_permissions_cache
10 after_create :maybe_invalidate_permissions_cache
11 after_destroy :maybe_invalidate_permissions_cache
12 attr_accessor :head_kind, :tail_kind
13 validate :name_link_has_valid_name
15 api_accessible :user, extend: :common do |t|
26 @properties ||= Hash.new
31 if k = ArvadosModel::resource_class_for_uuid(head_uuid)
37 if k = ArvadosModel::resource_class_for_uuid(tail_uuid)
44 def assign_generic_name_if_none_given
45 if new_record? and link_class == 'name' and (!name or name.empty?)
46 # Creating a name link with no name means "invent a generic
47 # name, like New Foo (1)"
49 head_class = ArvadosModel::resource_class_for_uuid(head_uuid)
51 errors.add :name, 'cannot be automatically assigned for this uuid'
54 name_base = "New " + head_class.to_s.underscore.humanize.downcase
55 if not Link.where(link_class: link_class,
60 # Find how many digits the largest N has among "New model (N)" names
61 maxlen = ActiveRecord::Base.connection.
62 execute("SELECT max(length(name)) maxlen FROM links "\
63 "WHERE link_class='name' "\
64 "AND tail_uuid=#{Link.sanitize(tail_uuid)} "\
65 "AND name~#{Link.sanitize "#{name_base} \\([0-9]+\\)"}")[0]
66 if maxlen and maxlen['maxlen']
67 # Find the largest N by sorting alphanumerically
68 maxname = ActiveRecord::Base.connection.
69 execute("SELECT max(name) maxname FROM links "\
70 "WHERE link_class='name' "\
71 "AND tail_uuid=#{Link.sanitize(tail_uuid)} "\
72 "AND length(name)=#{maxlen['maxlen']} "\
73 "AND name~#{Link.sanitize "#{name_base} \\([0-9]+\\)"}"
75 n = maxname.match(/\(([0-9]+)\)$/)[1].to_i
78 # "New foo" is taken, but "New foo (1)" isn't.
81 self.name = name_base + " (#{n})"
86 def permission_to_attach_to_objects
87 # Anonymous users cannot write links
88 return false if !current_user
90 # All users can write links that don't affect permissions
91 return true if self.link_class != 'permission'
93 # Administrators can grant permissions
94 return true if current_user.is_admin
96 # All users can grant permissions on objects they own
97 head_obj = self.class.
98 resource_class_for_uuid(self.head_uuid).
99 where('uuid=?',head_uuid).
102 return true if head_obj.owner_uuid == current_user.uuid
105 # Users with "can_grant" permission on an object can grant
106 # permissions on that object
107 has_grant_permission = self.class.
108 where('link_class=? AND name=? AND tail_uuid=? AND head_uuid=?',
109 'permission', 'can_grant', current_user.uuid, self.head_uuid).
111 return true if has_grant_permission
117 def maybe_invalidate_permissions_cache
118 if self.link_class == 'permission'
119 # Clearing the entire permissions cache can generate many
120 # unnecessary queries if many active users are not affected by
121 # this change. In such cases it would be better to search cached
122 # permissions for head_uuid and tail_uuid, and invalidate the
123 # cache for only those users. (This would require a browseable
125 User.invalidate_permissions_cache
129 def name_link_has_valid_name
130 if link_class == 'name'
131 if new_record? and (!name or name.empty?)
132 # Unique name will be assigned in before_create filter
135 unless name.is_a? String and !name.empty?
136 errors.add('name', 'must be a non-empty string')