Merge branch 'patch-1' of https://github.com/mr-c/arvados into mr-c-patch-1
[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   validate :permission_to_attach_to_objects
16   before_update :restrict_alter_permissions
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     if PERM_LEVEL[self.name].nil?
54       errors.add(:name, "is invalid permission, must be one of 'can_read', 'can_write', 'can_manage', 'can_login'")
55       return false
56     end
57
58     rsc_class = ArvadosModel::resource_class_for_uuid tail_uuid
59     if rsc_class == Group
60       tail_obj = Group.find_by_uuid(tail_uuid)
61       if tail_obj.nil?
62         errors.add(:tail_uuid, "does not exist")
63         return false
64       end
65       if tail_obj.group_class != "role"
66         errors.add(:tail_uuid, "must be a user or role, was group with group_class #{tail_obj.group_class}")
67         return false
68       end
69     elsif rsc_class != User
70       errors.add(:tail_uuid, "must be a user or role")
71       return false
72     end
73
74     # Administrators can grant permissions
75     return true if current_user.is_admin
76
77     head_obj = ArvadosModel.find_by_uuid(head_uuid)
78
79     # No permission links can be pointed to past collection versions
80     if head_obj.is_a?(Collection) && head_obj.current_version_uuid != head_uuid
81       errors.add(:head_uuid, "cannot point to a past version of a collection")
82       return false
83     end
84
85     # All users can grant permissions on objects they own or can manage
86     return true if current_user.can?(manage: head_obj)
87
88     # Default = deny.
89     false
90   end
91
92   def restrict_alter_permissions
93     return true if self.link_class != 'permission' && self.link_class_was != 'permission'
94
95     return true if current_user.andand.uuid == system_user.uuid
96
97     if link_class_changed? || tail_uuid_changed? || head_uuid_changed?
98       raise "Can only alter permission link level"
99     end
100   end
101
102   PERM_LEVEL = {
103     'can_read' => 1,
104     'can_login' => 1,
105     'can_write' => 2,
106     'can_manage' => 3,
107   }
108
109   def call_update_permissions
110     if self.link_class == 'permission'
111       update_permissions tail_uuid, head_uuid, PERM_LEVEL[name], self.uuid
112     end
113   end
114
115   def clear_permissions
116     if self.link_class == 'permission'
117       update_permissions tail_uuid, head_uuid, REVOKE_PERM, self.uuid
118     end
119   end
120
121   def check_permissions
122     if self.link_class == 'permission'
123       check_permissions_against_full_refresh
124     end
125   end
126
127   def name_links_are_obsolete
128     if link_class == 'name'
129       errors.add('name', 'Name links are obsolete')
130       false
131     else
132       true
133     end
134   end
135
136   # A user is permitted to create, update or modify a permission link
137   # if and only if they have "manage" permission on the object
138   # indicated by the permission link's head_uuid.
139   #
140   # All other links are treated as regular ArvadosModel objects.
141   #
142   def ensure_owner_uuid_is_permitted
143     if link_class == 'permission'
144       ob = ArvadosModel.find_by_uuid(head_uuid)
145       raise PermissionDeniedError unless current_user.can?(manage: ob)
146       # All permission links should be owned by the system user.
147       self.owner_uuid = system_user_uuid
148       return true
149     else
150       super
151     end
152   end
153 end