1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
6 require 'fix_roles_projects'
8 class GroupTest < ActiveSupport::TestCase
10 test "cannot set owner_uuid to object with existing ownership cycle" do
11 set_user_from_auth :active_trustedclient
13 # First make sure we have lots of permission on the bad group by
14 # renaming it to "{current name} is mine all mine"
15 g = groups(:bad_group_has_ownership_cycle_b)
16 g.name += " is mine all mine"
17 assert g.save, "active user should be able to modify group #{g.uuid}"
19 # Use the group as the owner of a new object
21 create(owner_uuid: groups(:bad_group_has_ownership_cycle_b).uuid)
22 assert s.valid?, "ownership should pass validation #{s.errors.messages}"
23 assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
25 # Use the group as the new owner of an existing object
26 s = specimens(:in_aproject)
27 s.owner_uuid = groups(:bad_group_has_ownership_cycle_b).uuid
28 assert s.valid?, "ownership should pass validation"
29 assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
32 test "cannot create a new ownership cycle" do
33 set_user_from_auth :active_trustedclient
35 g_foo = Group.create!(name: "foo", group_class: "project")
36 g_bar = Group.create!(name: "bar", group_class: "project")
38 g_foo.owner_uuid = g_bar.uuid
39 assert g_foo.save, lambda { g_foo.errors.messages }
40 g_bar.owner_uuid = g_foo.uuid
41 assert g_bar.valid?, "ownership cycle should not prevent validation"
42 assert_equal false, g_bar.save, "should not create an ownership loop"
43 assert g_bar.errors.messages[:owner_uuid].join(" ").match(/ownership cycle/)
46 test "cannot create a single-object ownership cycle" do
47 set_user_from_auth :active_trustedclient
49 g_foo = Group.create!(name: "foo", group_class: "project")
52 # Ensure I have permission to manage this group even when its owner changes
53 perm_link = Link.create!(tail_uuid: users(:active).uuid,
54 head_uuid: g_foo.uuid,
55 link_class: 'permission',
59 g_foo.owner_uuid = g_foo.uuid
60 assert_equal false, g_foo.save, "should not create an ownership loop"
61 assert g_foo.errors.messages[:owner_uuid].join(" ").match(/ownership cycle/)
64 test "cannot create a group that is not a 'role' or 'project'" do
65 set_user_from_auth :active_trustedclient
67 assert_raises(ActiveRecord::RecordInvalid) do
68 Group.create!(name: "foo")
71 assert_raises(ActiveRecord::RecordInvalid) do
72 Group.create!(name: "foo", group_class: "")
75 assert_raises(ActiveRecord::RecordInvalid) do
76 Group.create!(name: "foo", group_class: "bogus")
80 test "cannot change group_class on an already created group" do
81 set_user_from_auth :active_trustedclient
82 g = Group.create!(name: "foo", group_class: "role")
83 assert_raises(ActiveRecord::RecordInvalid) do
84 g.update_attributes!(group_class: "project")
88 test "role cannot own things" do
89 set_user_from_auth :active_trustedclient
90 role = Group.create!(name: "foo", group_class: "role")
91 assert_raises(ArvadosModel::PermissionDeniedError) do
92 Collection.create!(name: "bzzz123", owner_uuid: role.uuid)
95 c = Collection.create!(name: "bzzz124")
96 assert_raises(ArvadosModel::PermissionDeniedError) do
97 c.update_attributes!(owner_uuid: role.uuid)
101 test "trash group hides contents" do
102 set_user_from_auth :active_trustedclient
104 g_foo = Group.create!(name: "foo", group_class: "project")
105 col = Collection.create!(owner_uuid: g_foo.uuid)
107 assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
108 g_foo.update! is_trashed: true
109 assert Collection.readable_by(users(:active)).where(uuid: col.uuid).empty?
110 assert Collection.readable_by(users(:active), {:include_trash => true}).where(uuid: col.uuid).any?
111 g_foo.update! is_trashed: false
112 assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
115 test "trash group" do
116 set_user_from_auth :active_trustedclient
118 g_foo = Group.create!(name: "foo", group_class: "project")
119 g_bar = Group.create!(name: "bar", owner_uuid: g_foo.uuid, group_class: "project")
120 g_baz = Group.create!(name: "baz", owner_uuid: g_bar.uuid, group_class: "project")
122 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
123 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
124 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
125 g_foo.update! is_trashed: true
126 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).empty?
127 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
128 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
130 assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_foo.uuid).any?
131 assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_bar.uuid).any?
132 assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
136 test "trash subgroup" do
137 set_user_from_auth :active_trustedclient
139 g_foo = Group.create!(name: "foo", group_class: "project")
140 g_bar = Group.create!(name: "bar", owner_uuid: g_foo.uuid, group_class: "project")
141 g_baz = Group.create!(name: "baz", owner_uuid: g_bar.uuid, group_class: "project")
143 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
144 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
145 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
146 g_bar.update! is_trashed: true
148 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
149 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
150 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
152 assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_bar.uuid).any?
153 assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
156 test "trash subsubgroup" do
157 set_user_from_auth :active_trustedclient
159 g_foo = Group.create!(name: "foo", group_class: "project")
160 g_bar = Group.create!(name: "bar", owner_uuid: g_foo.uuid, group_class: "project")
161 g_baz = Group.create!(name: "baz", owner_uuid: g_bar.uuid, group_class: "project")
163 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
164 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
165 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
166 g_baz.update! is_trashed: true
167 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
168 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
169 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
170 assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
174 test "trash group propagates to subgroups" do
175 set_user_from_auth :active_trustedclient
177 g_foo = groups(:trashed_project)
178 g_bar = groups(:trashed_subproject)
179 g_baz = groups(:trashed_subproject3)
180 col = collections(:collection_in_trashed_subproject)
182 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).empty?
183 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
184 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
185 assert Collection.readable_by(users(:active)).where(uuid: col.uuid).empty?
187 set_user_from_auth :admin
188 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).empty?
189 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
190 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
191 assert Collection.readable_by(users(:active)).where(uuid: col.uuid).empty?
193 set_user_from_auth :active_trustedclient
194 g_foo.update! is_trashed: false
195 assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
196 assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
197 assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
199 # this one should still be trashed.
200 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
202 g_baz.update! is_trashed: false
203 assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
206 test "trashed does not propagate across permission links" do
207 set_user_from_auth :admin
209 g_foo = Group.create!(name: "foo", group_class: "role")
210 u_bar = User.create!(first_name: "bar")
212 assert Group.readable_by(users(:admin)).where(uuid: g_foo.uuid).any?
213 assert User.readable_by(users(:admin)).where(uuid: u_bar.uuid).any?
214 g_foo.update! is_trashed: true
216 assert Group.readable_by(users(:admin)).where(uuid: g_foo.uuid).empty?
217 assert User.readable_by(users(:admin)).where(uuid: u_bar.uuid).any?
219 g_foo.update! is_trashed: false
220 ln = Link.create!(tail_uuid: g_foo.uuid,
221 head_uuid: u_bar.uuid,
222 link_class: "permission",
224 g_foo.update! is_trashed: true
226 assert Group.readable_by(users(:admin)).where(uuid: g_foo.uuid).empty?
227 assert User.readable_by(users(:admin)).where(uuid: u_bar.uuid).any?
230 test "move projects to trash in SweepTrashedObjects" do
231 p = groups(:trashed_on_next_sweep)
232 assert_empty Group.where('uuid=? and is_trashed=true', p.uuid)
233 SweepTrashedObjects.sweep_now
234 assert_not_empty Group.where('uuid=? and is_trashed=true', p.uuid)
237 test "delete projects and their contents in SweepTrashedObjects" do
238 g_foo = groups(:trashed_project)
239 g_bar = groups(:trashed_subproject)
240 g_baz = groups(:trashed_subproject3)
241 col = collections(:collection_in_trashed_subproject)
242 job = jobs(:job_in_trashed_project)
243 cr = container_requests(:cr_in_trashed_project)
244 # Save how many objects were before the sweep
245 user_nr_was = User.all.length
246 coll_nr_was = Collection.all.length
247 group_nr_was = Group.where('group_class<>?', 'project').length
248 project_nr_was = Group.where(group_class: 'project').length
249 cr_nr_was = ContainerRequest.all.length
250 job_nr_was = Job.all.length
251 assert_not_empty Group.where(uuid: g_foo.uuid)
252 assert_not_empty Group.where(uuid: g_bar.uuid)
253 assert_not_empty Group.where(uuid: g_baz.uuid)
254 assert_not_empty Collection.where(uuid: col.uuid)
255 assert_not_empty Job.where(uuid: job.uuid)
256 assert_not_empty ContainerRequest.where(uuid: cr.uuid)
257 SweepTrashedObjects.sweep_now
258 assert_empty Group.where(uuid: g_foo.uuid)
259 assert_empty Group.where(uuid: g_bar.uuid)
260 assert_empty Group.where(uuid: g_baz.uuid)
261 assert_empty Collection.where(uuid: col.uuid)
262 assert_empty Job.where(uuid: job.uuid)
263 assert_empty ContainerRequest.where(uuid: cr.uuid)
264 # No unwanted deletions should have happened
265 assert_equal user_nr_was, User.all.length
266 assert_equal coll_nr_was-2, # collection_in_trashed_subproject
267 Collection.all.length # & deleted_on_next_sweep collections
268 assert_equal group_nr_was, Group.where('group_class<>?', 'project').length
269 assert_equal project_nr_was-3, Group.where(group_class: 'project').length
270 assert_equal cr_nr_was-1, ContainerRequest.all.length
271 assert_equal job_nr_was-1, Job.all.length
274 test "project names must be displayable in a filesystem" do
275 set_user_from_auth :active
276 ["", "{SOLIDUS}"].each do |subst|
277 Rails.configuration.Collections.ForwardSlashNameSubstitution = subst
278 proj = Group.create group_class: "project"
279 role = Group.create group_class: "role"
286 ["foo/bar", subst != ""],
287 ["../..", subst != ""],
289 ].each do |name, valid|
291 assert_equal true, role.valid?
293 assert_equal valid, proj.valid?, "#{name.inspect} should be #{valid ? "valid" : "invalid"}"
298 def insert_group uuid, owner_uuid, name, group_class
299 q = ActiveRecord::Base.connection.exec_query %{
300 insert into groups (uuid, owner_uuid, name, group_class, created_at, updated_at)
301 values ('#{uuid}', '#{owner_uuid}',
302 '#{name}', #{if group_class then "'"+group_class+"'" else 'NULL' end},
303 statement_timestamp(), statement_timestamp())
308 test "migration to fix roles and projects" do
309 g1 = insert_group Group.generate_uuid, system_user_uuid, 'group with no class', nil
310 g2 = insert_group Group.generate_uuid, users(:active).uuid, 'role owned by a user', 'role'
312 g3 = insert_group Group.generate_uuid, system_user_uuid, 'role that owns a project', 'role'
313 g4 = insert_group Group.generate_uuid, g3, 'the project', 'project'
315 g5 = insert_group Group.generate_uuid, users(:active).uuid, 'a project with an outgoing permission link', 'project'
317 g6 = insert_group Group.generate_uuid, system_user_uuid, 'name collision', 'role'
318 g7 = insert_group Group.generate_uuid, users(:active).uuid, 'name collision', 'role'
322 act_as_system_user do
323 l1 = Link.create!(link_class: 'permission', name: 'can_manage', tail_uuid: g3, head_uuid: g4)
324 q = ActiveRecord::Base.connection.exec_query %{
325 update links set tail_uuid='#{g5}' where uuid='#{l1.uuid}'
330 assert_equal nil, Group.find_by_uuid(g1).group_class
331 assert_equal users(:active).uuid, Group.find_by_uuid(g2).owner_uuid
332 assert_equal g3, Group.find_by_uuid(g4).owner_uuid
333 assert !Link.where(tail_uuid: users(:active).uuid, head_uuid: g2, link_class: "permission", name: "can_manage").any?
334 assert !Link.where(tail_uuid: g3, head_uuid: g4, link_class: "permission", name: "can_manage").any?
335 assert Link.where(link_class: 'permission', name: 'can_manage', tail_uuid: g5, head_uuid: g4).any?
339 assert_equal 'role', Group.find_by_uuid(g1).group_class
340 assert_equal system_user_uuid, Group.find_by_uuid(g2).owner_uuid
341 assert_equal system_user_uuid, Group.find_by_uuid(g4).owner_uuid
342 assert Link.where(tail_uuid: users(:active).uuid, head_uuid: g2, link_class: "permission", name: "can_manage").any?
343 assert Link.where(tail_uuid: g3, head_uuid: g4, link_class: "permission", name: "can_manage").any?
344 assert !Link.where(link_class: 'permission', name: 'can_manage', tail_uuid: g5, head_uuid: g4).any?