16007: Handle overlapping permissions correctly
[arvados.git] / services / api / test / unit / permission_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 PermissionTest < ActiveSupport::TestCase
8   include CurrentApiClient
9
10   test "Grant permissions on an object I own" do
11     set_user_from_auth :active_trustedclient
12
13     ob = Collection.create
14     assert ob.save
15
16     # Ensure I have permission to manage this group even when its owner changes
17     perm_link = Link.create(tail_uuid: users(:active).uuid,
18                             head_uuid: ob.uuid,
19                             link_class: 'permission',
20                             name: 'can_manage')
21     assert perm_link.save, "should give myself permission on my own object"
22   end
23
24   test "Delete permission links when deleting an object" do
25     set_user_from_auth :active_trustedclient
26
27     ob = Collection.create!
28     Link.create!(tail_uuid: users(:active).uuid,
29                  head_uuid: ob.uuid,
30                  link_class: 'permission',
31                  name: 'can_manage')
32     ob_uuid = ob.uuid
33     assert ob.destroy, "Could not destroy object with 1 permission link"
34     assert_empty(Link.where(head_uuid: ob_uuid),
35                  "Permission link was not deleted when object was deleted")
36   end
37
38   test "permission links owned by root" do
39     set_user_from_auth :active_trustedclient
40     ob = Collection.create!
41     perm_link = Link.create!(tail_uuid: users(:active).uuid,
42                              head_uuid: ob.uuid,
43                              link_class: 'permission',
44                              name: 'can_read')
45     assert_equal system_user_uuid, perm_link.owner_uuid
46   end
47
48   test "readable_by" do
49     set_user_from_auth :active_trustedclient
50
51     ob = Collection.create!
52     Link.create!(tail_uuid: users(:active).uuid,
53                  head_uuid: ob.uuid,
54                  link_class: 'permission',
55                  name: 'can_read')
56     assert Collection.readable_by(users(:active)).where(uuid: ob.uuid).any?, "user does not have read permission"
57   end
58
59   test "writable_by" do
60     set_user_from_auth :active_trustedclient
61
62     ob = Collection.create!
63     Link.create!(tail_uuid: users(:active).uuid,
64                  head_uuid: ob.uuid,
65                  link_class: 'permission',
66                  name: 'can_write')
67     assert ob.writable_by.include?(users(:active).uuid), "user does not have write permission"
68   end
69
70   test "update permission link" do
71     set_user_from_auth :admin
72
73     grp = Group.create! name: "blah project", group_class: "project"
74     ob = Collection.create! owner_uuid: grp.uuid
75
76     assert !users(:active).can?(write: ob)
77     assert !users(:active).can?(read: ob)
78
79     l1 = Link.create!(tail_uuid: users(:active).uuid,
80                  head_uuid: grp.uuid,
81                  link_class: 'permission',
82                  name: 'can_write')
83
84     assert users(:active).can?(write: ob)
85     assert users(:active).can?(read: ob)
86
87     l1.update_attributes!(name: 'can_read')
88
89     assert !users(:active).can?(write: ob)
90     assert users(:active).can?(read: ob)
91
92     l1.destroy
93
94     assert !users(:active).can?(write: ob)
95     assert !users(:active).can?(read: ob)
96   end
97
98   test "writable_by reports requesting user's own uuid for a writable project" do
99     invited_to_write = users(:project_viewer)
100     group = groups(:asubproject)
101
102     # project_view can read, but cannot see write or see writers list
103     set_user_from_auth :project_viewer
104     assert_equal([group.owner_uuid],
105                  group.writable_by,
106                  "writers list should just have owner_uuid")
107
108     # allow project_viewer to write for the remainder of the test
109     set_user_from_auth :admin
110     Link.create!(tail_uuid: invited_to_write.uuid,
111                  head_uuid: group.uuid,
112                  link_class: 'permission',
113                  name: 'can_write')
114     group.permissions.reload
115
116     # project_viewer should see self in writers list (but not all writers)
117     set_user_from_auth :project_viewer
118     assert_not_nil(group.writable_by,
119                     "can write but cannot see writers list")
120     assert_includes(group.writable_by, invited_to_write.uuid,
121                     "self missing from writers list")
122     assert_includes(group.writable_by, group.owner_uuid,
123                     "project owner missing from writers list")
124     refute_includes(group.writable_by, users(:active).uuid,
125                     "saw :active user in writers list")
126
127     # active user should see full writers list
128     set_user_from_auth :active
129     assert_includes(group.writable_by, invited_to_write.uuid,
130                     "permission just added, but missing from writers list")
131
132     # allow project_viewer to manage for the remainder of the test
133     set_user_from_auth :admin
134     Link.create!(tail_uuid: invited_to_write.uuid,
135                  head_uuid: group.uuid,
136                  link_class: 'permission',
137                  name: 'can_manage')
138     # invite another writer we can test for
139     Link.create!(tail_uuid: users(:spectator).uuid,
140                  head_uuid: group.uuid,
141                  link_class: 'permission',
142                  name: 'can_write')
143     group.permissions.reload
144
145     set_user_from_auth :project_viewer
146     assert_not_nil(group.writable_by,
147                     "can manage but cannot see writers list")
148     assert_includes(group.writable_by, users(:spectator).uuid,
149                     ":spectator missing from writers list")
150   end
151
152   test "user owns group, group can_manage object's group, user can add permissions" do
153     set_user_from_auth :admin
154
155     owner_grp = Group.create!(owner_uuid: users(:active).uuid, group_class: "role")
156     sp_grp = Group.create!(group_class: "project")
157
158     Link.create!(link_class: 'permission',
159                  name: 'can_manage',
160                  tail_uuid: owner_grp.uuid,
161                  head_uuid: sp_grp.uuid)
162
163     sp = Collection.create!(owner_uuid: sp_grp.uuid)
164
165     # active user owns owner_grp, which has can_manage permission on sp_grp
166     # user should be able to add permissions on sp.
167     set_user_from_auth :active_trustedclient
168     test_perm = Link.create(tail_uuid: users(:active).uuid,
169                             head_uuid: sp.uuid,
170                             link_class: 'permission',
171                             name: 'can_write')
172     assert test_perm.save, "could not save new permission on target object"
173     assert test_perm.destroy, "could not delete new permission on target object"
174   end
175
176   # bug #3091
177   skip "can_manage permission on a non-group object" do
178     set_user_from_auth :admin
179
180     ob = Collection.create!
181     # grant can_manage permission to active
182     perm_link = Link.create!(tail_uuid: users(:active).uuid,
183                              head_uuid: ob.uuid,
184                              link_class: 'permission',
185                              name: 'can_manage')
186     # ob is owned by :admin, the link is owned by root
187     assert_equal users(:admin).uuid, ob.owner_uuid
188     assert_equal system_user_uuid, perm_link.owner_uuid
189
190     # user "active" can modify the permission link
191     set_user_from_auth :active_trustedclient
192     perm_link.properties["foo"] = 'bar'
193     assert perm_link.save, "could not save modified link"
194
195     assert_equal 'bar', perm_link.properties['foo'], "link properties do not include foo = bar"
196   end
197
198   test "user without can_manage permission may not modify permission link" do
199     set_user_from_auth :admin
200
201     ob = Collection.create!
202     # grant can_manage permission to active
203     perm_link = Link.create!(tail_uuid: users(:active).uuid,
204                              head_uuid: ob.uuid,
205                              link_class: 'permission',
206                              name: 'can_read')
207     # ob is owned by :admin, the link is owned by root
208     assert_equal ob.owner_uuid, users(:admin).uuid
209     assert_equal perm_link.owner_uuid, system_user_uuid
210
211     # user "active" may not modify the permission link
212     set_user_from_auth :active_trustedclient
213     perm_link.name = 'can_manage'
214     assert_raises ArvadosModel::PermissionDeniedError do
215       perm_link.save
216     end
217   end
218
219   test "manager user gets permission to minions' articles via can_manage link" do
220     manager = create :active_user, first_name: "Manage", last_name: "Er"
221     minion = create :active_user, first_name: "Min", last_name: "Ion"
222     minions_specimen = act_as_user minion do
223       g = Group.create! name: "minon project", group_class: "project"
224       Collection.create! owner_uuid: g.uuid
225     end
226     # Manager creates a group. (Make sure it doesn't magically give
227     # anyone any additional permissions.)
228     g = nil
229     act_as_user manager do
230       g = create :group, name: "NoBigSecret Lab"
231       assert_empty(User.readable_by(manager).where(uuid: minion.uuid),
232                    "saw a user I shouldn't see")
233       assert_raises(ArvadosModel::PermissionDeniedError,
234                     ActiveRecord::RecordInvalid,
235                     "gave can_read permission to a user I shouldn't see") do
236         create(:permission_link,
237                name: 'can_read', tail_uuid: minion.uuid, head_uuid: g.uuid)
238       end
239       %w(can_manage can_write can_read).each do |perm_type|
240         assert_raises(ArvadosModel::PermissionDeniedError,
241                       ActiveRecord::RecordInvalid,
242                       "escalated privileges") do
243           create(:permission_link,
244                  name: perm_type, tail_uuid: g.uuid, head_uuid: minion.uuid)
245         end
246       end
247       assert_empty(User.readable_by(manager).where(uuid: minion.uuid),
248                    "manager saw minion too soon")
249       assert_empty(User.readable_by(minion).where(uuid: manager.uuid),
250                    "minion saw manager too soon")
251       assert_empty(Group.readable_by(minion).where(uuid: g.uuid),
252                    "minion saw manager's new NoBigSecret Lab group too soon")
253
254       # Manager declares everybody on the system should be able to see
255       # the NoBigSecret Lab group.
256       create(:permission_link,
257              name: 'can_read',
258              tail_uuid: 'zzzzz-j7d0g-fffffffffffffff',
259              head_uuid: g.uuid)
260       # ...but nobody has joined the group yet. Manager still can't see
261       # minion.
262       assert_empty(User.readable_by(manager).where(uuid: minion.uuid),
263                    "manager saw minion too soon")
264     end
265
266     act_as_user minion do
267       # Minion can see the group.
268       assert_not_empty(Group.readable_by(minion).where(uuid: g.uuid),
269                        "minion could not see the NoBigSecret Lab group")
270       # Minion joins the group.
271       create(:permission_link,
272              name: 'can_read',
273              tail_uuid: g.uuid,
274              head_uuid: minion.uuid)
275     end
276
277     act_as_user manager do
278       # Now, manager can see minion.
279       assert_not_empty(User.readable_by(manager).where(uuid: minion.uuid),
280                        "manager could not see minion")
281       # But cannot obtain further privileges this way.
282       assert_raises(ArvadosModel::PermissionDeniedError,
283                     "escalated privileges") do
284         create(:permission_link,
285                name: 'can_manage', tail_uuid: manager.uuid, head_uuid: minion.uuid)
286       end
287       assert_empty(Collection
288                      .readable_by(manager)
289                      .where(uuid: minions_specimen.uuid),
290                    "manager saw the minion's private stuff")
291       assert_raises(ArvadosModel::PermissionDeniedError,
292                    "manager could update minion's private stuff") do
293         minions_specimen.update_attributes(properties: {'x' => 'y'})
294       end
295     end
296
297     act_as_system_user do
298       # Root can give Manager more privileges over Minion.
299       create(:permission_link,
300              name: 'can_manage', tail_uuid: g.uuid, head_uuid: minion.uuid)
301     end
302
303     act_as_user manager do
304       # Now, manager can read and write Minion's stuff.
305       assert_not_empty(Collection
306                          .readable_by(manager)
307                          .where(uuid: minions_specimen.uuid),
308                        "manager could not find minion's specimen by uuid")
309       assert_equal(true,
310                    minions_specimen.update_attributes(properties: {'x' => 'y'}),
311                    "manager could not update minion's specimen object")
312     end
313   end
314
315   test "users with bidirectional read permission in group can see each other, but cannot see each other's private articles" do
316     a = create :active_user, first_name: "A"
317     b = create :active_user, first_name: "B"
318     other = create :active_user, first_name: "OTHER"
319
320     assert_empty(User.readable_by(b).where(uuid: a.uuid),
321                      "#{b.first_name} should not be able to see 'a' in the user list")
322     assert_empty(User.readable_by(a).where(uuid: b.uuid),
323                      "#{a.first_name} should not be able to see 'b' in the user list")
324
325     act_as_system_user do
326       g = create :group
327       [a,b].each do |u|
328         create(:permission_link,
329                name: 'can_read', tail_uuid: u.uuid, head_uuid: g.uuid)
330         create(:permission_link,
331                name: 'can_read', head_uuid: u.uuid, tail_uuid: g.uuid)
332       end
333     end
334
335     assert_not_empty(User.readable_by(b).where(uuid: a.uuid),
336                      "#{b.first_name} should be able to see 'a' in the user list")
337     assert_not_empty(User.readable_by(a).where(uuid: b.uuid),
338                      "#{a.first_name} should be able to see 'b' in the user list")
339
340     a_specimen = act_as_user a do
341       Collection.create!
342     end
343     assert_not_empty(Collection.readable_by(a).where(uuid: a_specimen.uuid),
344                      "A cannot read own Collection, following test probably useless.")
345     assert_empty(Collection.readable_by(b).where(uuid: a_specimen.uuid),
346                  "B can read A's Collection")
347     [a,b].each do |u|
348       assert_empty(User.readable_by(u).where(uuid: other.uuid),
349                    "#{u.first_name} can see OTHER in the user list")
350       assert_empty(User.readable_by(other).where(uuid: u.uuid),
351                    "OTHER can see #{u.first_name} in the user list")
352       act_as_user u do
353         assert_raises ArvadosModel::PermissionDeniedError, "wrote without perm" do
354           other.update_attributes!(prefs: {'pwned' => true})
355         end
356         assert_equal(true, u.update_attributes!(prefs: {'thisisme' => true}),
357                      "#{u.first_name} can't update its own prefs")
358       end
359       act_as_user other do
360         assert_raises(ArvadosModel::PermissionDeniedError,
361                         "OTHER wrote #{u.first_name} without perm") do
362           u.update_attributes!(prefs: {'pwned' => true})
363         end
364         assert_equal(true, other.update_attributes!(prefs: {'thisisme' => true}),
365                      "OTHER can't update its own prefs")
366       end
367     end
368   end
369
370   test "cannot create with owner = unwritable user" do
371     set_user_from_auth :rominiadmin
372     assert_raises ArvadosModel::PermissionDeniedError, "created with owner = unwritable user" do
373       Collection.create!(owner_uuid: users(:active).uuid)
374     end
375   end
376
377   test "cannot change owner to unwritable user" do
378     set_user_from_auth :rominiadmin
379     ob = Collection.create!
380     assert_raises ArvadosModel::PermissionDeniedError, "changed owner to unwritable user" do
381       ob.update_attributes!(owner_uuid: users(:active).uuid)
382     end
383   end
384
385   test "cannot create with owner = unwritable group" do
386     set_user_from_auth :rominiadmin
387     assert_raises ArvadosModel::PermissionDeniedError, "created with owner = unwritable group" do
388       Collection.create!(owner_uuid: groups(:aproject).uuid)
389     end
390   end
391
392   test "cannot change owner to unwritable group" do
393     set_user_from_auth :rominiadmin
394     ob = Collection.create!
395     assert_raises ArvadosModel::PermissionDeniedError, "changed owner to unwritable group" do
396       ob.update_attributes!(owner_uuid: groups(:aproject).uuid)
397     end
398   end
399
400   def container_logs(container, user)
401     Log.readable_by(users(user)).
402       where(object_uuid: containers(container).uuid, event_type: "test")
403   end
404
405   test "container logs created by dispatch are visible to container requestor" do
406     set_user_from_auth :dispatch1
407     Log.create!(object_uuid: containers(:running).uuid,
408                 event_type: "test")
409
410     assert_not_empty container_logs(:running, :admin)
411     assert_not_empty container_logs(:running, :active)
412     assert_empty container_logs(:running, :spectator)
413   end
414
415   test "container logs created by dispatch are public if container request is public" do
416     set_user_from_auth :dispatch1
417     Log.create!(object_uuid: containers(:running_older).uuid,
418                 event_type: "test")
419
420     assert_not_empty container_logs(:running_older, :anonymous)
421   end
422
423   test "add user to group, then remove them" do
424     set_user_from_auth :admin
425     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "role")
426     col = Collection.create!(owner_uuid: grp.uuid)
427     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
428     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
429
430     l1 = Link.create!(tail_uuid: users(:active).uuid,
431                  head_uuid: grp.uuid,
432                  link_class: 'permission',
433                  name: 'can_read')
434     l2 = Link.create!(tail_uuid: grp.uuid,
435                  head_uuid: users(:active).uuid,
436                  link_class: 'permission',
437                  name: 'can_read')
438
439     l3 = Link.create!(tail_uuid: users(:project_viewer).uuid,
440                  head_uuid: grp.uuid,
441                  link_class: 'permission',
442                  name: 'can_read')
443     l4 = Link.create!(tail_uuid: grp.uuid,
444                  head_uuid: users(:project_viewer).uuid,
445                  link_class: 'permission',
446                  name: 'can_read')
447
448     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
449     assert User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid).first
450
451     l1.destroy
452     l2.destroy
453
454     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
455     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
456
457   end
458
459
460   test "add user to group, then change permission level" do
461     set_user_from_auth :admin
462     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "role")
463     col = Collection.create!(owner_uuid: grp.uuid)
464     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
465     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
466
467     l1 = Link.create!(tail_uuid: users(:active).uuid,
468                  head_uuid: grp.uuid,
469                  link_class: 'permission',
470                  name: 'can_manage')
471     l2 = Link.create!(tail_uuid: grp.uuid,
472                  head_uuid: users(:active).uuid,
473                  link_class: 'permission',
474                  name: 'can_read')
475
476     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
477     assert users(:active).can?(read: col.uuid)
478     assert users(:active).can?(write: col.uuid)
479     assert users(:active).can?(manage: col.uuid)
480
481     l1.name = 'can_read'
482     l1.save!
483
484     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
485     assert users(:active).can?(read: col.uuid)
486     assert !users(:active).can?(write: col.uuid)
487     assert !users(:active).can?(manage: col.uuid)
488
489     l1.name = 'can_write'
490     l1.save!
491
492     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
493     assert users(:active).can?(read: col.uuid)
494     assert users(:active).can?(write: col.uuid)
495     assert !users(:active).can?(manage: col.uuid)
496   end
497
498
499   test "add user to group, then add overlapping permission link to group" do
500     set_user_from_auth :admin
501     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "role")
502     col = Collection.create!(owner_uuid: grp.uuid)
503     assert_empty Collection.readable_by(users(:active)).where(uuid: col.uuid)
504     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
505
506     l1 = Link.create!(tail_uuid: users(:active).uuid,
507                  head_uuid: grp.uuid,
508                  link_class: 'permission',
509                  name: 'can_manage')
510     l2 = Link.create!(tail_uuid: grp.uuid,
511                  head_uuid: users(:active).uuid,
512                  link_class: 'permission',
513                  name: 'can_read')
514
515     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
516     assert users(:active).can?(read: col.uuid)
517     assert users(:active).can?(write: col.uuid)
518     assert users(:active).can?(manage: col.uuid)
519
520     l3 = Link.create!(tail_uuid: users(:active).uuid,
521                  head_uuid: grp.uuid,
522                  link_class: 'permission',
523                  name: 'can_read')
524
525     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
526     assert users(:active).can?(read: col.uuid)
527     assert users(:active).can?(write: col.uuid)
528     assert users(:active).can?(manage: col.uuid)
529
530     l3.destroy!
531
532     assert Collection.readable_by(users(:active)).where(uuid: col.uuid).first
533     assert users(:active).can?(read: col.uuid)
534     assert users(:active).can?(write: col.uuid)
535     assert users(:active).can?(manage: col.uuid)
536   end
537
538
539   test "add user to group, then add overlapping permission link to subproject" do
540     set_user_from_auth :admin
541     grp = Group.create!(owner_uuid: system_user_uuid, group_class: "project")
542     prj = Group.create!(owner_uuid: grp.uuid, group_class: "project")
543     assert_empty Group.readable_by(users(:active)).where(uuid: prj.uuid)
544     assert_empty User.readable_by(users(:active)).where(uuid: users(:project_viewer).uuid)
545
546     l1 = Link.create!(tail_uuid: users(:active).uuid,
547                  head_uuid: grp.uuid,
548                  link_class: 'permission',
549                  name: 'can_manage')
550     l2 = Link.create!(tail_uuid: grp.uuid,
551                  head_uuid: users(:active).uuid,
552                  link_class: 'permission',
553                  name: 'can_read')
554
555     assert Group.readable_by(users(:active)).where(uuid: prj.uuid).first
556     assert users(:active).can?(read: prj.uuid)
557     assert users(:active).can?(write: prj.uuid)
558     assert users(:active).can?(manage: prj.uuid)
559
560     l3 = Link.create!(tail_uuid: grp.uuid,
561                  head_uuid: prj.uuid,
562                  link_class: 'permission',
563                  name: 'can_read')
564
565     assert Group.readable_by(users(:active)).where(uuid: prj.uuid).first
566     assert users(:active).can?(read: prj.uuid)
567     assert users(:active).can?(write: prj.uuid)
568     assert users(:active).can?(manage: prj.uuid)
569
570     l3.destroy!
571
572     assert Group.readable_by(users(:active)).where(uuid: prj.uuid).first
573     assert users(:active).can?(read: prj.uuid)
574     assert users(:active).can?(write: prj.uuid)
575     assert users(:active).can?(manage: prj.uuid)
576   end
577 end