2872: Ensure name link tail_uuid == owner_uuid. refs #2872
[arvados.git] / services / api / app / models / link.rb
1 class Link < ArvadosModel
2   include HasUuid
3   include KindAndEtag
4   include CommonApiTemplate
5   serialize :properties, Hash
6   before_create :permission_to_attach_to_objects
7   before_update :permission_to_attach_to_objects
8   after_update :maybe_invalidate_permissions_cache
9   after_create :maybe_invalidate_permissions_cache
10   after_destroy :maybe_invalidate_permissions_cache
11   attr_accessor :head_kind, :tail_kind
12   validate :name_link_has_valid_name
13   validate :name_link_owner_is_tail
14
15   api_accessible :user, extend: :common do |t|
16     t.add :tail_uuid
17     t.add :link_class
18     t.add :name
19     t.add :head_uuid
20     t.add :head_kind
21     t.add :tail_kind
22     t.add :properties
23   end
24
25   def properties
26     @properties ||= Hash.new
27     super
28   end
29
30   def head_kind
31     if k = ArvadosModel::resource_class_for_uuid(head_uuid)
32       k.kind
33     end
34   end
35
36   def tail_kind
37     if k = ArvadosModel::resource_class_for_uuid(tail_uuid)
38       k.kind
39     end
40   end
41
42   protected
43
44   def permission_to_attach_to_objects
45     # Anonymous users cannot write links
46     return false if !current_user
47
48     # All users can write links that don't affect permissions
49     return true if self.link_class != 'permission'
50
51     # Administrators can grant permissions
52     return true if current_user.is_admin
53
54     # All users can grant permissions on objects they own
55     head_obj = self.class.
56       resource_class_for_uuid(self.head_uuid).
57       where('uuid=?',head_uuid).
58       first
59     if head_obj
60       return true if head_obj.owner_uuid == current_user.uuid
61     end
62
63     # Users with "can_grant" permission on an object can grant
64     # permissions on that object
65     has_grant_permission = self.class.
66       where('link_class=? AND name=? AND tail_uuid=? AND head_uuid=?',
67             'permission', 'can_grant', current_user.uuid, self.head_uuid).
68       count > 0
69     return true if has_grant_permission
70
71     # Default = deny.
72     false
73   end
74
75   def maybe_invalidate_permissions_cache
76     if self.link_class == 'permission'
77       # Clearing the entire permissions cache can generate many
78       # unnecessary queries if many active users are not affected by
79       # this change. In such cases it would be better to search cached
80       # permissions for head_uuid and tail_uuid, and invalidate the
81       # cache for only those users. (This would require a browseable
82       # cache.)
83       User.invalidate_permissions_cache
84     end
85   end
86
87   def name_link_has_valid_name
88     if link_class == 'name'
89       unless name.is_a? String and !name.empty?
90         errors.add('name', 'must be a non-empty string')
91       end
92     else
93       true
94     end
95   end
96
97   def name_link_owner_is_tail
98     if link_class == 'name'
99       self.owner_uuid = tail_uuid
100       ensure_owner_uuid_is_permitted
101     end
102   end
103 end