Merge branch '21205-ensure-unique' refs #21205
[arvados.git] / services / api / test / functional / arvados / v1 / links_controller_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 Arvados::V1::LinksControllerTest < ActionController::TestCase
8
9   ['link', 'link_json'].each do |formatted_link|
10     test "no symbol keys in serialized hash #{formatted_link}" do
11       link = {
12         properties: {username: 'testusername'},
13         link_class: 'test',
14         name: 'encoding',
15         tail_uuid: users(:admin).uuid,
16         head_uuid: virtual_machines(:testvm).uuid
17       }
18       authorize_with :admin
19       if formatted_link == 'link_json'
20         post :create, params: {link: link.to_json}
21       else
22         post :create, params: {link: link}
23       end
24       assert_response :success
25       assert_not_nil assigns(:object)
26       assert_equal 'testusername', assigns(:object).properties['username']
27       assert_equal false, assigns(:object).properties.has_key?(:username)
28     end
29   end
30
31   %w(created_at modified_at).each do |attr|
32     {nil: nil, bogus: 2.days.ago}.each do |bogustype, bogusvalue|
33       test "cannot set #{bogustype} #{attr} in create" do
34         authorize_with :active
35         post :create, params: {
36           link: {
37             properties: {},
38             link_class: 'test',
39             name: 'test',
40           }.merge(attr => bogusvalue)
41         }
42         assert_response :success
43         resp = JSON.parse @response.body
44         assert_in_delta Time.now, Time.parse(resp[attr]), 3.0
45       end
46       test "cannot set #{bogustype} #{attr} in update" do
47         really_created_at = links(:test_timestamps).created_at
48         authorize_with :active
49         put :update, params: {
50           id: links(:test_timestamps).uuid,
51           link: {
52             :properties => {test: 'test'},
53             attr => bogusvalue
54           }
55         }
56         assert_response :success
57         resp = JSON.parse @response.body
58         case attr
59         when 'created_at'
60           assert_in_delta really_created_at, Time.parse(resp[attr]), 0.001
61         else
62           assert_in_delta Time.now, Time.parse(resp[attr]), 3.0
63         end
64       end
65     end
66   end
67
68   test "head must exist" do
69     link = {
70       link_class: 'test',
71       name: 'stuff',
72       tail_uuid: users(:active).uuid,
73       head_uuid: 'zzzzz-tpzed-xyzxyzxerrrorxx'
74     }
75     authorize_with :admin
76     post :create, params: {link: link}
77     assert_response 422
78   end
79
80   test "tail must exist" do
81     link = {
82       link_class: 'test',
83       name: 'stuff',
84       head_uuid: users(:active).uuid,
85       tail_uuid: 'zzzzz-tpzed-xyzxyzxerrrorxx'
86     }
87     authorize_with :admin
88     post :create, params: {link: link}
89     assert_response 422
90   end
91
92   test "head and tail exist, head_kind and tail_kind are returned" do
93     link = {
94       link_class: 'test',
95       name: 'stuff',
96       head_uuid: users(:active).uuid,
97       tail_uuid: users(:spectator).uuid,
98     }
99     authorize_with :admin
100     post :create, params: {link: link}
101     assert_response :success
102     l = JSON.parse(@response.body)
103     assert 'arvados#user', l['head_kind']
104     assert 'arvados#user', l['tail_kind']
105   end
106
107   test "can supply head_kind and tail_kind without error" do
108     link = {
109       link_class: 'test',
110       name: 'stuff',
111       head_uuid: users(:active).uuid,
112       tail_uuid: users(:spectator).uuid,
113       head_kind: "arvados#user",
114       tail_kind: "arvados#user",
115     }
116     authorize_with :admin
117     post :create, params: {link: link}
118     assert_response :success
119     l = JSON.parse(@response.body)
120     assert 'arvados#user', l['head_kind']
121     assert 'arvados#user', l['tail_kind']
122   end
123
124   test "tail must be visible by user" do
125     link = {
126       link_class: 'test',
127       name: 'stuff',
128       head_uuid: users(:active).uuid,
129       tail_uuid: authorized_keys(:admin).uuid,
130     }
131     authorize_with :active
132     post :create, params: {link: link}
133     assert_response 422
134   end
135
136   test "filter links with 'is_a' operator" do
137     authorize_with :admin
138     get :index, params: {
139       filters: [ ['tail_uuid', 'is_a', 'arvados#user'] ]
140     }
141     assert_response :success
142     found = assigns(:objects)
143     assert_not_equal 0, found.count
144     assert_equal found.count, (found.select { |f| f.tail_uuid.match User.uuid_regex }).count
145   end
146
147   test "filter links with 'is_a' operator includes remote objects" do
148     authorize_with :admin
149     get :index, params: {
150       filters: [
151         ['tail_uuid', 'is_a', 'arvados#user'],
152         ['link_class', '=', 'permission'],
153         ['name', '=', 'can_read'],
154         ['head_uuid', '=', collections(:foo_file).uuid],
155       ]
156     }
157     assert_response :success
158     found = assigns(:objects)
159     assert_not_equal 0, found.count
160     assert_includes(found.map(&:tail_uuid),
161                     users(:federated_active).uuid)
162   end
163
164   test "filter links with 'is_a' operator with more than one" do
165     authorize_with :admin
166     get :index, params: {
167       filters: [ ['tail_uuid', 'is_a', ['arvados#user', 'arvados#group'] ] ],
168     }
169     assert_response :success
170     found = assigns(:objects)
171     assert_not_equal 0, found.count
172     assert_equal found.count, (found.select { |f|
173                                  f.tail_uuid.match User.uuid_regex or
174                                  f.tail_uuid.match Group.uuid_regex
175                                }).count
176   end
177
178   test "filter links with 'is_a' operator with bogus type" do
179     authorize_with :admin
180     get :index, params: {
181       filters: [ ['tail_uuid', 'is_a', ['arvados#bogus'] ] ],
182     }
183     assert_response :success
184     found = assigns(:objects)
185     assert_equal 0, found.count
186   end
187
188   test "filter links with 'is_a' operator with collection" do
189     authorize_with :admin
190     get :index, params: {
191       filters: [ ['head_uuid', 'is_a', ['arvados#collection'] ] ],
192     }
193     assert_response :success
194     found = assigns(:objects)
195     assert_not_equal 0, found.count
196     assert_equal found.count, (found.select { |f| f.head_uuid.match Collection.uuid_regex}).count
197   end
198
199   test "test can still use where tail_kind" do
200     authorize_with :admin
201     get :index, params: {
202       where: { tail_kind: 'arvados#user' }
203     }
204     assert_response :success
205     found = assigns(:objects)
206     assert_not_equal 0, found.count
207     assert_equal found.count, (found.select { |f| f.tail_uuid.match User.uuid_regex }).count
208   end
209
210   test "test can still use where head_kind" do
211     authorize_with :admin
212     get :index, params: {
213       where: { head_kind: 'arvados#user' }
214     }
215     assert_response :success
216     found = assigns(:objects)
217     assert_not_equal 0, found.count
218     assert_equal found.count, (found.select { |f| f.head_uuid.match User.uuid_regex }).count
219   end
220
221   test "test can still use filter tail_kind" do
222     authorize_with :admin
223     get :index, params: {
224       filters: [ ['tail_kind', '=', 'arvados#user'] ]
225     }
226     assert_response :success
227     found = assigns(:objects)
228     assert_not_equal 0, found.count
229     assert_equal found.count, (found.select { |f| f.tail_uuid.match User.uuid_regex }).count
230   end
231
232   test "test can still use filter head_kind" do
233     authorize_with :admin
234     get :index, params: {
235       filters: [ ['head_kind', '=', 'arvados#user'] ]
236     }
237     assert_response :success
238     found = assigns(:objects)
239     assert_not_equal 0, found.count
240     assert_equal found.count, (found.select { |f| f.head_uuid.match User.uuid_regex }).count
241   end
242
243   test "head_kind matches head_uuid" do
244     link = {
245       link_class: 'test',
246       name: 'stuff',
247       head_uuid: groups(:public).uuid,
248       head_kind: "arvados#user",
249       tail_uuid: users(:spectator).uuid,
250       tail_kind: "arvados#user",
251     }
252     authorize_with :admin
253     post :create, params: {link: link}
254     assert_response 422
255   end
256
257   test "tail_kind matches tail_uuid" do
258     link = {
259       link_class: 'test',
260       name: 'stuff',
261       head_uuid: users(:active).uuid,
262       head_kind: "arvados#user",
263       tail_uuid: groups(:public).uuid,
264       tail_kind: "arvados#user",
265     }
266     authorize_with :admin
267     post :create, params: {link: link}
268     assert_response 422
269   end
270
271   test "test with virtual_machine" do
272     link = {
273       tail_kind: "arvados#user",
274       tail_uuid: users(:active).uuid,
275       head_kind: "arvados#virtual_machine",
276       head_uuid: virtual_machines(:testvm).uuid,
277       link_class: "permission",
278       name: "can_login",
279       properties: {username: "repo_and_user_name"}
280     }
281     authorize_with :admin
282     post :create, params: {link: link}
283     assert_response 422
284   end
285
286   test "test with virtualMachine" do
287     link = {
288       tail_kind: "arvados#user",
289       tail_uuid: users(:active).uuid,
290       head_kind: "arvados#virtualMachine",
291       head_uuid: virtual_machines(:testvm).uuid,
292       link_class: "permission",
293       name: "can_login",
294       properties: {username: "repo_and_user_name"}
295     }
296     authorize_with :admin
297     post :create, params: {link: link}
298     assert_response :success
299   end
300
301   test "project owner can show a project permission" do
302     uuid = links(:project_viewer_can_read_project).uuid
303     authorize_with :active
304     get :show, params: {id: uuid}
305     assert_response :success
306     assert_equal(uuid, assigns(:object).andand.uuid)
307   end
308
309   test "admin can show a project permission" do
310     uuid = links(:project_viewer_can_read_project).uuid
311     authorize_with :admin
312     get :show, params: {id: uuid}
313     assert_response :success
314     assert_equal(uuid, assigns(:object).andand.uuid)
315   end
316
317   test "project viewer can't show others' project permissions" do
318     authorize_with :project_viewer
319     get :show, params: {id: links(:admin_can_write_aproject).uuid}
320     assert_response 404
321   end
322
323   test "requesting a nonexistent link returns 404" do
324     authorize_with :active
325     get :show, params: {id: 'zzzzz-zzzzz-zzzzzzzzzzzzzzz'}
326     assert_response 404
327   end
328
329   # not implemented
330   skip "retrieve all permissions using generic links index api" do
331     # Links.readable_by() does not return the full set of permission
332     # links that are visible to a user (i.e., all permission links
333     # whose head_uuid references an object for which the user has
334     # ownership or can_manage permission). Therefore, neither does
335     # /arvados/v1/links.
336     #
337     # It is possible to retrieve the full set of permissions for a
338     # single object via /arvados/v1/permissions.
339     authorize_with :active
340     get :index, params: {
341       filters: [['link_class', '=', 'permission'],
342                 ['head_uuid', '=', groups(:aproject).uuid]]
343     }
344     assert_response :success
345     assert_not_nil assigns(:objects)
346     assert_includes(assigns(:objects).map(&:uuid),
347                     links(:project_viewer_can_read_project).uuid)
348   end
349
350   test "admin can index project permissions" do
351     authorize_with :admin
352     get :index, params: {
353       filters: [['link_class', '=', 'permission'],
354                 ['head_uuid', '=', groups(:aproject).uuid]]
355     }
356     assert_response :success
357     assert_not_nil assigns(:objects)
358     assert_includes(assigns(:objects).map(&:uuid),
359                     links(:project_viewer_can_read_project).uuid)
360   end
361
362   test "project viewer can't index others' project permissions" do
363     authorize_with :project_viewer
364     get :index, params: {
365       filters: [['link_class', '=', 'permission'],
366                 ['head_uuid', '=', groups(:aproject).uuid],
367                 ['tail_uuid', '!=', users(:project_viewer).uuid]]
368     }
369     assert_response :success
370     assert_not_nil assigns(:objects)
371     assert_empty assigns(:objects)
372   end
373
374   # Granting permissions.
375   test "grant can_read on project to other users in group" do
376     authorize_with :user_foo_in_sharing_group
377
378     refute users(:user_bar_in_sharing_group).can?(read: collections(:collection_owned_by_foo).uuid)
379
380     post :create, params: {
381       link: {
382         tail_uuid: users(:user_bar_in_sharing_group).uuid,
383         link_class: 'permission',
384         name: 'can_read',
385         head_uuid: collections(:collection_owned_by_foo).uuid,
386       }
387     }
388     assert_response :success
389     assert users(:user_bar_in_sharing_group).can?(read: collections(:collection_owned_by_foo).uuid)
390   end
391 end