16007: Make it so that only projects can own things
[arvados.git] / services / api / test / unit / group_test.rb
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: AGPL-3.0
4
5 require 'test_helper'
6
7 class GroupTest < ActiveSupport::TestCase
8
9   test "cannot set owner_uuid to object with existing ownership cycle" do
10     set_user_from_auth :active_trustedclient
11
12     # First make sure we have lots of permission on the bad group by
13     # renaming it to "{current name} is mine all mine"
14     g = groups(:bad_group_has_ownership_cycle_b)
15     g.name += " is mine all mine"
16     assert g.save, "active user should be able to modify group #{g.uuid}"
17
18     # Use the group as the owner of a new object
19     s = Specimen.
20       create(owner_uuid: groups(:bad_group_has_ownership_cycle_b).uuid)
21     assert s.valid?, "ownership should pass validation #{s.errors.messages}"
22     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
23
24     # Use the group as the new owner of an existing object
25     s = specimens(:in_aproject)
26     s.owner_uuid = groups(:bad_group_has_ownership_cycle_b).uuid
27     assert s.valid?, "ownership should pass validation"
28     assert_equal false, s.save, "should not save object with #{g.uuid} as owner"
29   end
30
31   test "cannot create a new ownership cycle" do
32     set_user_from_auth :active_trustedclient
33
34     g_foo = Group.create!(name: "foo", group_class: "project")
35     g_bar = Group.create!(name: "bar", group_class: "project")
36
37     g_foo.owner_uuid = g_bar.uuid
38     assert g_foo.save, lambda { g_foo.errors.messages }
39     g_bar.owner_uuid = g_foo.uuid
40     assert g_bar.valid?, "ownership cycle should not prevent validation"
41     assert_equal false, g_bar.save, "should not create an ownership loop"
42     assert g_bar.errors.messages[:owner_uuid].join(" ").match(/ownership cycle/)
43   end
44
45   test "cannot create a single-object ownership cycle" do
46     set_user_from_auth :active_trustedclient
47
48     g_foo = Group.create!(name: "foo", group_class: "project")
49     assert g_foo.save
50
51     # Ensure I have permission to manage this group even when its owner changes
52     perm_link = Link.create!(tail_uuid: users(:active).uuid,
53                             head_uuid: g_foo.uuid,
54                             link_class: 'permission',
55                             name: 'can_manage')
56     assert perm_link.save
57
58     g_foo.owner_uuid = g_foo.uuid
59     assert_equal false, g_foo.save, "should not create an ownership loop"
60     assert g_foo.errors.messages[:owner_uuid].join(" ").match(/ownership cycle/)
61   end
62
63   test "cannot create a group that is not a 'role' or 'project'" do
64     set_user_from_auth :active_trustedclient
65
66     assert_raises(ActiveRecord::RecordInvalid) do
67       Group.create!(name: "foo")
68     end
69
70     assert_raises(ActiveRecord::RecordInvalid) do
71       Group.create!(name: "foo", group_class: "")
72     end
73
74     assert_raises(ActiveRecord::RecordInvalid) do
75       Group.create!(name: "foo", group_class: "bogus")
76     end
77   end
78
79   test "cannot change group_class on an already created group" do
80     set_user_from_auth :active_trustedclient
81     g = Group.create!(name: "foo", group_class: "role")
82     assert_raises(ActiveRecord::RecordInvalid) do
83       g.update_attributes!(group_class: "project")
84     end
85   end
86
87   test "role cannot own things" do
88     set_user_from_auth :active_trustedclient
89     role = Group.create!(name: "foo", group_class: "role")
90     assert_raises(ArvadosModel::PermissionDeniedError) do
91       Collection.create!(name: "bzzz123", owner_uuid: role.uuid)
92     end
93
94     c = Collection.create!(name: "bzzz124")
95     assert_raises(ArvadosModel::PermissionDeniedError) do
96       c.update_attributes!(owner_uuid: role.uuid)
97     end
98   end
99
100   test "trash group hides contents" do
101     set_user_from_auth :active_trustedclient
102
103     g_foo = Group.create!(name: "foo", group_class: "project")
104     col = Collection.create!(owner_uuid: g_foo.uuid)
105
106     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
107     g_foo.update! is_trashed: true
108     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).empty?
109     assert Collection.readable_by(users(:active), {:include_trash => true}).where(uuid: col.uuid).any?
110     g_foo.update! is_trashed: false
111     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
112   end
113
114   test "trash group" do
115     set_user_from_auth :active_trustedclient
116
117     g_foo = Group.create!(name: "foo", group_class: "project")
118     g_bar = Group.create!(name: "bar", owner_uuid: g_foo.uuid, group_class: "project")
119     g_baz = Group.create!(name: "baz", owner_uuid: g_bar.uuid, group_class: "project")
120
121     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
122     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
123     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
124     g_foo.update! is_trashed: true
125     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).empty?
126     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
127     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
128
129     assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_foo.uuid).any?
130     assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_bar.uuid).any?
131     assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
132   end
133
134
135   test "trash subgroup" do
136     set_user_from_auth :active_trustedclient
137
138     g_foo = Group.create!(name: "foo", group_class: "project")
139     g_bar = Group.create!(name: "bar", owner_uuid: g_foo.uuid, group_class: "project")
140     g_baz = Group.create!(name: "baz", owner_uuid: g_bar.uuid, group_class: "project")
141
142     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
143     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
144     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
145     g_bar.update! is_trashed: true
146
147     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
148     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
149     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
150
151     assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_bar.uuid).any?
152     assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
153   end
154
155   test "trash subsubgroup" do
156     set_user_from_auth :active_trustedclient
157
158     g_foo = Group.create!(name: "foo", group_class: "project")
159     g_bar = Group.create!(name: "bar", owner_uuid: g_foo.uuid, group_class: "project")
160     g_baz = Group.create!(name: "baz", owner_uuid: g_bar.uuid, group_class: "project")
161
162     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
163     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
164     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
165     g_baz.update! is_trashed: true
166     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
167     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
168     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
169     assert Group.readable_by(users(:active), {:include_trash => true}).where(uuid: g_baz.uuid).any?
170   end
171
172
173   test "trash group propagates to subgroups" do
174     set_user_from_auth :active_trustedclient
175
176     g_foo = groups(:trashed_project)
177     g_bar = groups(:trashed_subproject)
178     g_baz = groups(:trashed_subproject3)
179     col = collections(:collection_in_trashed_subproject)
180
181     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).empty?
182     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
183     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
184     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).empty?
185
186     set_user_from_auth :admin
187     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).empty?
188     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).empty?
189     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
190     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).empty?
191
192     set_user_from_auth :active_trustedclient
193     g_foo.update! is_trashed: false
194     assert Group.readable_by(users(:active)).where(uuid: g_foo.uuid).any?
195     assert Group.readable_by(users(:active)).where(uuid: g_bar.uuid).any?
196     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).any?
197
198     # this one should still be trashed.
199     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).empty?
200
201     g_baz.update! is_trashed: false
202     assert Group.readable_by(users(:active)).where(uuid: g_baz.uuid).any?
203   end
204
205   test "trashed does not propagate across permission links" do
206     set_user_from_auth :admin
207
208     g_foo = Group.create!(name: "foo", group_class: "project")
209     u_bar = User.create!(first_name: "bar")
210
211     assert Group.readable_by(users(:admin)).where(uuid: g_foo.uuid).any?
212     assert User.readable_by(users(:admin)).where(uuid:  u_bar.uuid).any?
213     g_foo.update! is_trashed: true
214
215     assert Group.readable_by(users(:admin)).where(uuid: g_foo.uuid).empty?
216     assert User.readable_by(users(:admin)).where(uuid:  u_bar.uuid).any?
217
218     g_foo.update! is_trashed: false
219     ln = Link.create!(tail_uuid: g_foo.uuid,
220                       head_uuid: u_bar.uuid,
221                       link_class: "permission",
222                       name: "can_read")
223     g_foo.update! is_trashed: true
224
225     assert Group.readable_by(users(:admin)).where(uuid: g_foo.uuid).empty?
226     assert User.readable_by(users(:admin)).where(uuid:  u_bar.uuid).any?
227   end
228
229   test "move projects to trash in SweepTrashedObjects" do
230     p = groups(:trashed_on_next_sweep)
231     assert_empty Group.where('uuid=? and is_trashed=true', p.uuid)
232     SweepTrashedObjects.sweep_now
233     assert_not_empty Group.where('uuid=? and is_trashed=true', p.uuid)
234   end
235
236   test "delete projects and their contents in SweepTrashedObjects" do
237     g_foo = groups(:trashed_project)
238     g_bar = groups(:trashed_subproject)
239     g_baz = groups(:trashed_subproject3)
240     col = collections(:collection_in_trashed_subproject)
241     job = jobs(:job_in_trashed_project)
242     cr = container_requests(:cr_in_trashed_project)
243     # Save how many objects were before the sweep
244     user_nr_was = User.all.length
245     coll_nr_was = Collection.all.length
246     group_nr_was = Group.where('group_class<>?', 'project').length
247     project_nr_was = Group.where(group_class: 'project').length
248     cr_nr_was = ContainerRequest.all.length
249     job_nr_was = Job.all.length
250     assert_not_empty Group.where(uuid: g_foo.uuid)
251     assert_not_empty Group.where(uuid: g_bar.uuid)
252     assert_not_empty Group.where(uuid: g_baz.uuid)
253     assert_not_empty Collection.where(uuid: col.uuid)
254     assert_not_empty Job.where(uuid: job.uuid)
255     assert_not_empty ContainerRequest.where(uuid: cr.uuid)
256     SweepTrashedObjects.sweep_now
257     assert_empty Group.where(uuid: g_foo.uuid)
258     assert_empty Group.where(uuid: g_bar.uuid)
259     assert_empty Group.where(uuid: g_baz.uuid)
260     assert_empty Collection.where(uuid: col.uuid)
261     assert_empty Job.where(uuid: job.uuid)
262     assert_empty ContainerRequest.where(uuid: cr.uuid)
263     # No unwanted deletions should have happened
264     assert_equal user_nr_was, User.all.length
265     assert_equal coll_nr_was-2,        # collection_in_trashed_subproject
266                  Collection.all.length # & deleted_on_next_sweep collections
267     assert_equal group_nr_was, Group.where('group_class<>?', 'project').length
268     assert_equal project_nr_was-3, Group.where(group_class: 'project').length
269     assert_equal cr_nr_was-1, ContainerRequest.all.length
270     assert_equal job_nr_was-1, Job.all.length
271   end
272
273   test "project names must be displayable in a filesystem" do
274     set_user_from_auth :active
275     ["", "{SOLIDUS}"].each do |subst|
276       Rails.configuration.Collections.ForwardSlashNameSubstitution = subst
277       proj = Group.create group_class: "project"
278       role = Group.create group_class: "role"
279       [[nil, true],
280        ["", true],
281        [".", false],
282        ["..", false],
283        ["...", true],
284        ["..z..", true],
285        ["foo/bar", subst != ""],
286        ["../..", subst != ""],
287        ["/", subst != ""],
288       ].each do |name, valid|
289         role.name = name
290         assert_equal true, role.valid?
291         proj.name = name
292         assert_equal valid, proj.valid?, "#{name.inspect} should be #{valid ? "valid" : "invalid"}"
293       end
294     end
295   end
296 end