16007: Handle overlapping permissions correctly
[arvados.git] / services / api / app / models / link.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 class Link < ArvadosModel
6   include HasUuid
7   include KindAndEtag
8   include CommonApiTemplate
9
10   # Posgresql JSONB columns should NOT be declared as serialized, Rails 5
11   # already know how to properly treat them.
12   attribute :properties, :jsonbHash, default: {}
13
14   validate :name_links_are_obsolete
15   before_create :permission_to_attach_to_objects
16   before_update :permission_to_attach_to_objects
17   after_update :call_update_permissions
18   after_create :call_update_permissions
19   before_destroy :clear_permissions
20   after_destroy :check_permissions
21
22   api_accessible :user, extend: :common do |t|
23     t.add :tail_uuid
24     t.add :link_class
25     t.add :name
26     t.add :head_uuid
27     t.add :head_kind
28     t.add :tail_kind
29     t.add :properties
30   end
31
32   def head_kind
33     if k = ArvadosModel::resource_class_for_uuid(head_uuid)
34       k.kind
35     end
36   end
37
38   def tail_kind
39     if k = ArvadosModel::resource_class_for_uuid(tail_uuid)
40       k.kind
41     end
42   end
43
44   protected
45
46   def permission_to_attach_to_objects
47     # Anonymous users cannot write links
48     return false if !current_user
49
50     # All users can write links that don't affect permissions
51     return true if self.link_class != 'permission'
52
53     # Administrators can grant permissions
54     return true if current_user.is_admin
55
56     head_obj = ArvadosModel.find_by_uuid(head_uuid)
57
58     # No permission links can be pointed to past collection versions
59     return false if head_obj.is_a?(Collection) && head_obj.current_version_uuid != head_uuid
60
61     # All users can grant permissions on objects they own or can manage
62     return true if current_user.can?(manage: head_obj)
63
64     # Default = deny.
65     false
66   end
67
68   PERM_LEVEL = {
69     'can_read' => 1,
70     'can_login' => 1,
71     'can_write' => 2,
72     'can_manage' => 3,
73   }
74
75   def call_update_permissions
76     if self.link_class == 'permission'
77       update_permissions tail_uuid, head_uuid, PERM_LEVEL[name], self.uuid
78     end
79   end
80
81   def clear_permissions
82     if self.link_class == 'permission'
83       update_permissions tail_uuid, head_uuid, REVOKE_PERM, self.uuid
84     end
85   end
86
87   def check_permissions
88     if self.link_class == 'permission'
89       check_permissions_against_full_refresh
90     end
91   end
92
93   def name_links_are_obsolete
94     if link_class == 'name'
95       errors.add('name', 'Name links are obsolete')
96       false
97     else
98       true
99     end
100   end
101
102   # A user is permitted to create, update or modify a permission link
103   # if and only if they have "manage" permission on the object
104   # indicated by the permission link's head_uuid.
105   #
106   # All other links are treated as regular ArvadosModel objects.
107   #
108   def ensure_owner_uuid_is_permitted
109     if link_class == 'permission'
110       ob = ArvadosModel.find_by_uuid(head_uuid)
111       raise PermissionDeniedError unless current_user.can?(manage: ob)
112       # All permission links should be owned by the system user.
113       self.owner_uuid = system_user_uuid
114       return true
115     else
116       super
117     end
118   end
119 end