Merge branch '21660-inode-test-race'
[arvados.git] / services / api / test / integration / permissions_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 PermissionsTest < ActionDispatch::IntegrationTest
8   include DbCurrentTime
9   fixtures :users, :groups, :api_client_authorizations, :collections
10
11   test "adding and removing direct can_read links" do
12     # try to read collection as spectator
13     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
14       params: {:format => :json},
15       headers: auth(:spectator)
16     assert_response 404
17
18     # try to add permission as spectator
19     post "/arvados/v1/links",
20       params: {
21         :format => :json,
22         :link => {
23           tail_uuid: users(:spectator).uuid,
24           link_class: 'permission',
25           name: 'can_read',
26           head_uuid: collections(:foo_file).uuid,
27           properties: {}
28         }
29       },
30       headers: auth(:spectator)
31     assert_response 422
32
33     # add permission as admin
34     post "/arvados/v1/links",
35       params: {
36         :format => :json,
37         :link => {
38           tail_uuid: users(:spectator).uuid,
39           link_class: 'permission',
40           name: 'can_read',
41           head_uuid: collections(:foo_file).uuid,
42           properties: {}
43         }
44       },
45       headers: auth(:admin)
46     u = json_response['uuid']
47     assert_response :success
48
49     # read collection as spectator
50     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
51       params: {:format => :json},
52       headers: auth(:spectator)
53     assert_response :success
54
55     # try to delete permission as spectator
56     delete "/arvados/v1/links/#{u}",
57       params: {:format => :json},
58       headers: auth(:spectator)
59     assert_response 403
60
61     # delete permission as admin
62     delete "/arvados/v1/links/#{u}",
63       params: {:format => :json},
64       headers: auth(:admin)
65     assert_response :success
66
67     # try to read collection as spectator
68     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
69       params: {:format => :json},
70       headers: auth(:spectator)
71     assert_response 404
72   end
73
74
75   test "adding can_read links from user to group, group to collection" do
76     # try to read collection as spectator
77     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
78       params: {:format => :json},
79       headers: auth(:spectator)
80     assert_response 404
81
82     # add permission for spectator to read group
83     post "/arvados/v1/links",
84       params: {
85         :format => :json,
86         :link => {
87           tail_uuid: users(:spectator).uuid,
88           link_class: 'permission',
89           name: 'can_read',
90           head_uuid: groups(:private_role).uuid,
91           properties: {}
92         }
93       },
94       headers: auth(:admin)
95     assert_response :success
96
97     # try to read collection as spectator
98     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
99       params: {:format => :json},
100       headers: auth(:spectator)
101     assert_response 404
102
103     # add permission for group to read collection
104     post "/arvados/v1/links",
105       params: {
106         :format => :json,
107         :link => {
108           tail_uuid: groups(:private_role).uuid,
109           link_class: 'permission',
110           name: 'can_read',
111           head_uuid: collections(:foo_file).uuid,
112           properties: {}
113         }
114       },
115       headers: auth(:admin)
116     u = json_response['uuid']
117     assert_response :success
118
119     # try to read collection as spectator
120     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
121       params: {:format => :json},
122       headers: auth(:spectator)
123     assert_response :success
124
125     # delete permission for group to read collection
126     delete "/arvados/v1/links/#{u}",
127       params: {:format => :json},
128       headers: auth(:admin)
129     assert_response :success
130
131     # try to read collection as spectator
132     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
133       params: {:format => :json},
134       headers: auth(:spectator)
135     assert_response 404
136
137   end
138
139
140   test "adding can_read links from group to collection, user to group" do
141     # try to read collection as spectator
142     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
143       params: {:format => :json},
144       headers: auth(:spectator)
145     assert_response 404
146
147     # add permission for group to read collection
148     post "/arvados/v1/links",
149       params: {
150         :format => :json,
151         :link => {
152           tail_uuid: groups(:private_role).uuid,
153           link_class: 'permission',
154           name: 'can_read',
155           head_uuid: collections(:foo_file).uuid,
156           properties: {}
157         }
158       },
159       headers: auth(:admin)
160     assert_response :success
161
162     # try to read collection as spectator
163     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
164       params: {:format => :json},
165       headers: auth(:spectator)
166     assert_response 404
167
168     # add permission for spectator to read group
169     post "/arvados/v1/links",
170       params: {
171         :format => :json,
172         :link => {
173           tail_uuid: users(:spectator).uuid,
174           link_class: 'permission',
175           name: 'can_read',
176           head_uuid: groups(:private_role).uuid,
177           properties: {}
178         }
179       },
180       headers: auth(:admin)
181     u = json_response['uuid']
182     assert_response :success
183
184     # try to read collection as spectator
185     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
186       params: {:format => :json},
187       headers: auth(:spectator)
188     assert_response :success
189
190     # delete permission for spectator to read group
191     delete "/arvados/v1/links/#{u}",
192       params: {:format => :json},
193       headers: auth(:admin)
194     assert_response :success
195
196     # try to read collection as spectator
197     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
198       params: {:format => :json},
199       headers: auth(:spectator)
200     assert_response 404
201
202   end
203
204   test "adding can_read links from user to group, group to group, group to collection" do
205     # try to read collection as spectator
206     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
207       params: {:format => :json},
208       headers: auth(:spectator)
209     assert_response 404
210
211     # add permission for user to read group
212     post "/arvados/v1/links",
213       params: {
214         :format => :json,
215         :link => {
216           tail_uuid: users(:spectator).uuid,
217           link_class: 'permission',
218           name: 'can_read',
219           head_uuid: groups(:private_role).uuid,
220           properties: {}
221         }
222       },
223       headers: auth(:admin)
224     assert_response :success
225
226     # add permission for group to read group
227     post "/arvados/v1/links",
228       params: {
229         :format => :json,
230         :link => {
231           tail_uuid: groups(:private_role).uuid,
232           link_class: 'permission',
233           name: 'can_read',
234           head_uuid: groups(:empty_lonely_group).uuid,
235           properties: {}
236         }
237       },
238       headers: auth(:admin)
239     assert_response :success
240
241     # add permission for group to read collection
242     post "/arvados/v1/links",
243       params: {
244         :format => :json,
245         :link => {
246           tail_uuid: groups(:empty_lonely_group).uuid,
247           link_class: 'permission',
248           name: 'can_read',
249           head_uuid: collections(:foo_file).uuid,
250           properties: {}
251         }
252       },
253       headers: auth(:admin)
254     u = json_response['uuid']
255     assert_response :success
256
257     # try to read collection as spectator
258     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
259       params: {:format => :json},
260       headers: auth(:spectator)
261     assert_response :success
262
263     # delete permission for group to read collection
264     delete "/arvados/v1/links/#{u}",
265       params: {:format => :json},
266       headers: auth(:admin)
267     assert_response :success
268
269     # try to read collection as spectator
270     get "/arvados/v1/collections/#{collections(:foo_file).uuid}",
271       params: {:format => :json},
272       headers: auth(:spectator)
273     assert_response 404
274   end
275
276   test "read-only group-admin cannot modify administered user" do
277     put "/arvados/v1/users/#{users(:active).uuid}",
278       params: {
279         :user => {
280           first_name: 'KilroyWasHere'
281         },
282         :format => :json
283       },
284       headers: auth(:rominiadmin)
285     assert_response 403
286   end
287
288   test "read-only group-admin cannot read or update non-administered user" do
289     get "/arvados/v1/users/#{users(:spectator).uuid}",
290       params: {:format => :json},
291       headers: auth(:rominiadmin)
292     assert_response 404
293
294     put "/arvados/v1/users/#{users(:spectator).uuid}",
295       params: {
296         :user => {
297           first_name: 'KilroyWasHere'
298         },
299         :format => :json
300       },
301       headers: auth(:rominiadmin)
302     assert_response 404
303   end
304
305   test "RO group-admin finds user's collections, RW group-admin can update" do
306     other_user_collection = act_as_user(users(:user_foo_in_sharing_group)) do
307       Collection.create()
308     end
309     [[:rominiadmin, false],
310      [:miniadmin, true]].each do |which_user, update_should_succeed|
311       get "/arvados/v1/collections",
312         params: {:format => :json},
313         headers: auth(which_user)
314       assert_response :success
315       resp_uuids = json_response['items'].collect { |i| i['uuid'] }
316       [[true, collections(:collection_owned_by_active).uuid],
317        [true, collections(:foo_collection_in_aproject).uuid],
318        [false, other_user_collection.uuid],
319       ].each do |should_find, uuid|
320         assert_equal(should_find, !resp_uuids.index(uuid).nil?,
321                      "%s should%s see %s in collection list" %
322                      [which_user.to_s,
323                       should_find ? '' : ' not',
324                       uuid])
325         put "/arvados/v1/collections/#{uuid}",
326           params: {
327             :collection => {
328               properties: {
329                 miniadmin_was_here: true
330               }
331             },
332             :format => :json
333           },
334           headers: auth(which_user)
335         if !should_find
336           assert_response 404
337         elsif !update_should_succeed
338           assert_response 403
339         else
340           assert_response :success
341         end
342       end
343     end
344   end
345
346   test "get_permissions returns list" do
347     # First confirm that user :active cannot get permissions on group :public
348     get "/arvados/v1/permissions/#{groups(:public).uuid}",
349       params: nil,
350       headers: auth(:active)
351     assert_response 404
352
353     get "/arvados/v1/links",
354         params: {
355           :filters => [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
356         },
357       headers: auth(:active)
358     assert_response :success
359     assert_equal [], json_response['items']
360
361     ### add some permissions, including can_manage
362     ### permission for user :active
363     post "/arvados/v1/links",
364       params: {
365         :format => :json,
366         :link => {
367           tail_uuid: users(:spectator).uuid,
368           link_class: 'permission',
369           name: 'can_read',
370           head_uuid: groups(:public).uuid,
371           properties: {}
372         }
373       },
374       headers: auth(:admin)
375     assert_response :success
376     can_read_uuid = json_response['uuid']
377
378     post "/arvados/v1/links",
379       params: {
380         :format => :json,
381         :link => {
382           tail_uuid: users(:inactive).uuid,
383           link_class: 'permission',
384           name: 'can_write',
385           head_uuid: groups(:public).uuid,
386           properties: {}
387         }
388       },
389       headers: auth(:admin)
390     assert_response :success
391     can_write_uuid = json_response['uuid']
392
393     # Still should not be able read these permission links
394     get "/arvados/v1/permissions/#{groups(:public).uuid}",
395       params: nil,
396       headers: auth(:active)
397     assert_response 404
398
399     get "/arvados/v1/links",
400         params: {
401           :filters => [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
402         },
403       headers: auth(:active)
404     assert_response :success
405     assert_equal [], json_response['items']
406
407     # Shouldn't be able to read links directly either
408     get "/arvados/v1/links/#{can_read_uuid}",
409         params: {},
410       headers: auth(:active)
411     assert_response 404
412
413     ### Now add a can_manage link
414     post "/arvados/v1/links",
415       params: {
416         :format => :json,
417         :link => {
418           tail_uuid: users(:active).uuid,
419           link_class: 'permission',
420           name: 'can_manage',
421           head_uuid: groups(:public).uuid,
422           properties: {}
423         }
424       },
425       headers: auth(:admin)
426     assert_response :success
427     can_manage_uuid = json_response['uuid']
428
429     # user :active should be able to retrieve permissions
430     # on group :public using get_permissions
431     get("/arvados/v1/permissions/#{groups(:public).uuid}",
432       params: { :format => :json },
433       headers: auth(:active))
434     assert_response :success
435
436     perm_uuids = json_response['items'].map { |item| item['uuid'] }
437     assert_includes perm_uuids, can_read_uuid, "can_read_uuid not found"
438     assert_includes perm_uuids, can_write_uuid, "can_write_uuid not found"
439     assert_includes perm_uuids, can_manage_uuid, "can_manage_uuid not found"
440
441     # user :active should be able to retrieve permissions
442     # on group :public using link list
443     get "/arvados/v1/links",
444         params: {
445           :filters => [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
446         },
447       headers: auth(:active)
448     assert_response :success
449
450     perm_uuids = json_response['items'].map { |item| item['uuid'] }
451     assert_includes perm_uuids, can_read_uuid, "can_read_uuid not found"
452     assert_includes perm_uuids, can_write_uuid, "can_write_uuid not found"
453     assert_includes perm_uuids, can_manage_uuid, "can_manage_uuid not found"
454
455     # Should be able to read links directly too
456     get "/arvados/v1/links/#{can_read_uuid}",
457       headers: auth(:active)
458     assert_response :success
459
460     ### Create some objects of different types (other than projects)
461     ### inside a subproject inside the shared project, and share those
462     ### individual objects with a 3rd user ("spectator").
463     post '/arvados/v1/groups',
464          params: {
465            group: {
466              owner_uuid: groups(:public).uuid,
467              name: 'permission test subproject',
468              group_class: 'project',
469            },
470          },
471          headers: auth(:admin)
472     assert_response :success
473     subproject_uuid = json_response['uuid']
474
475     test_types = ['collection', 'workflow', 'container_request']
476     test_type_create_attrs = {
477       'container_request' => {
478         command: ["echo", "foo"],
479         container_image: links(:docker_image_collection_tag).name,
480         cwd: "/tmp",
481         environment: {},
482         mounts: {"/out" => {kind: "tmp", capacity: 1000000}},
483         output_path: "/out",
484         runtime_constraints: {"vcpus" => 1, "ram" => 2},
485       },
486     }
487
488     test_object = {}
489     test_object_perm_link = {}
490     test_types.each do |test_type|
491       post "/arvados/v1/#{test_type}s",
492            params: {
493              test_type.to_sym => {
494                owner_uuid: subproject_uuid,
495                name: "permission test #{test_type} in subproject",
496              }.merge(test_type_create_attrs[test_type] || {}).to_json,
497            },
498            headers: auth(:admin)
499       assert_response :success
500       test_object[test_type] = json_response
501
502       post '/arvados/v1/links',
503            params: {
504              link: {
505                tail_uuid: users(:spectator).uuid,
506                link_class: 'permission',
507                name: 'can_read',
508                head_uuid: test_object[test_type]['uuid'],
509              }
510            },
511            headers: auth(:admin)
512       assert_response :success
513       test_object_perm_link[test_type] = json_response
514     end
515
516     # The "active-can_manage-project" permission should cause the
517     # "spectator-can_read-object" links to be visible to the "active"
518     # user.
519     test_types.each do |test_type|
520       get "/arvados/v1/permissions/#{test_object[test_type]['uuid']}",
521           headers: auth(:active)
522       assert_response :success
523       perm_uuids = json_response['items'].map { |item| item['uuid'] }
524       assert_includes perm_uuids, test_object_perm_link[test_type]['uuid'], "can_read_uuid not found"
525
526       get "/arvados/v1/links/#{test_object_perm_link[test_type]['uuid']}",
527           headers: auth(:active)
528       assert_response :success
529
530       [
531         ['head_uuid', '=', test_object[test_type]['uuid']],
532         ['head_uuid', 'in', [test_object[test_type]['uuid']]],
533         ['head_uuid', 'in', [users(:admin).uuid, test_object[test_type]['uuid']]],
534       ].each do |filter|
535         get "/arvados/v1/links",
536             params: {
537               filters: ([['link_class', '=', 'permission'], filter]).to_json,
538             },
539             headers: auth(:active)
540         assert_response :success
541         assert_not_empty json_response['items'], "could not find can_read link using index with filter #{filter}"
542         assert_equal test_object_perm_link[test_type]['uuid'], json_response['items'][0]['uuid']
543       end
544
545       # The "spectator-can_read-object" link should be visible to the
546       # subject user ("spectator") in a filter query, even without
547       # can_manage permission on the target object.
548       [
549         ['tail_uuid', '=', users(:spectator).uuid],
550       ].each do |filter|
551         get "/arvados/v1/links",
552             params: {
553               filters: ([['link_class', '=', 'permission'], filter]).to_json,
554             },
555             headers: auth(:spectator)
556         assert_response :success
557         perm_uuids = json_response['items'].map { |item| item['uuid'] }
558         assert_includes perm_uuids, test_object_perm_link[test_type]['uuid'], "could not find can_read link using index with filter #{filter}"
559       end
560     end
561
562     ### Now delete the can_manage link
563     delete "/arvados/v1/links/#{can_manage_uuid}",
564       headers: auth(:active)
565     assert_response :success
566
567     # Should not be able read these permission links again
568     test_types.each do |test_type|
569       get "/arvados/v1/permissions/#{groups(:public).uuid}",
570           headers: auth(:active)
571       assert_response 404
572
573       get "/arvados/v1/permissions/#{test_object[test_type]['uuid']}",
574           headers: auth(:active)
575       assert_response 404
576
577       get "/arvados/v1/links",
578           params: {
579             filters: [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
580           },
581           headers: auth(:active)
582       assert_response :success
583       assert_equal [], json_response['items']
584
585       [
586         ['head_uuid', '=', test_object[test_type]['uuid']],
587         ['head_uuid', 'in', [users(:admin).uuid, test_object[test_type]['uuid']]],
588         ['head_uuid', 'in', []],
589       ].each do |filter|
590         get "/arvados/v1/links",
591             params: {
592               :filters => [["link_class", "=", "permission"], filter].to_json
593             },
594             headers: auth(:active)
595         assert_response :success
596         assert_equal [], json_response['items']
597       end
598
599       # Should not be able to read links directly either
600       get "/arvados/v1/links/#{can_read_uuid}",
601           headers: auth(:active)
602       assert_response 404
603
604       test_types.each do |test_type|
605         get "/arvados/v1/links/#{test_object_perm_link[test_type]['uuid']}",
606             headers: auth(:active)
607         assert_response 404
608       end
609     end
610
611     ### Create a collection, and share it with a direct permission
612     ### link (as opposed to sharing its parent project)
613     post "/arvados/v1/collections",
614       params: {
615         collection: {
616           name: 'permission test',
617         }
618       },
619       headers: auth(:admin)
620     assert_response :success
621     collection_uuid = json_response['uuid']
622     post "/arvados/v1/links",
623       params: {
624         link: {
625           tail_uuid: users(:spectator).uuid,
626           link_class: 'permission',
627           name: 'can_read',
628           head_uuid: collection_uuid,
629           properties: {}
630         }
631       },
632       headers: auth(:admin)
633     assert_response :success
634     can_read_collection_uuid = json_response['uuid']
635
636     # Should not be able read the permission link via permissions API,
637     # because permission is only can_read, not can_manage
638     get "/arvados/v1/permissions/#{collection_uuid}",
639       headers: auth(:active)
640     assert_response 404
641
642     # Should not be able to read the permission link directly, for
643     # same reason
644     get "/arvados/v1/links/#{can_read_collection_uuid}",
645       headers: auth(:active)
646     assert_response 404
647
648     ### Now add a can_manage link
649     post "/arvados/v1/links",
650       params: {
651         link: {
652           tail_uuid: users(:active).uuid,
653           link_class: 'permission',
654           name: 'can_manage',
655           head_uuid: collection_uuid,
656           properties: {}
657         }
658       },
659       headers: auth(:admin)
660     assert_response :success
661     can_manage_collection_uuid = json_response['uuid']
662
663     # Should be able read both permission links via permissions API
664     get "/arvados/v1/permissions/#{collection_uuid}",
665       headers: auth(:active)
666     assert_response :success
667     perm_uuids = json_response['items'].map { |item| item['uuid'] }
668     assert_includes perm_uuids, can_read_collection_uuid, "can_read_uuid not found"
669     assert_includes perm_uuids, can_manage_collection_uuid, "can_manage_uuid not found"
670
671     # Should be able to read both permission links directly
672     [can_read_collection_uuid, can_manage_collection_uuid].each do |uuid|
673       get "/arvados/v1/links/#{uuid}",
674         headers: auth(:active)
675       assert_response :success
676     end
677   end
678
679   test "get_permissions returns 404 for nonexistent uuid" do
680     nonexistent = Group.generate_uuid
681     # make sure it really doesn't exist
682     get "/arvados/v1/groups/#{nonexistent}", params: nil, headers: auth(:admin)
683     assert_response 404
684
685     get "/arvados/v1/permissions/#{nonexistent}", params: nil, headers: auth(:active)
686     assert_response 404
687   end
688
689   test "get_permissions returns 403 if user can read but not manage" do
690     post "/arvados/v1/links",
691       params: {
692         :link => {
693           tail_uuid: users(:active).uuid,
694           link_class: 'permission',
695           name: 'can_read',
696           head_uuid: groups(:public).uuid,
697           properties: {}
698         }
699       },
700       headers: auth(:admin)
701     assert_response :success
702
703     get "/arvados/v1/permissions/#{groups(:public).uuid}",
704       params: nil,
705       headers: auth(:active)
706     assert_response 403
707   end
708
709   test "active user can read the empty collection" do
710     # The active user should be able to read the empty collection.
711
712     get("/arvados/v1/collections/#{empty_collection_pdh}",
713       params: {:format => :json},
714       headers: auth(:active))
715     assert_response :success
716     assert_empty json_response['manifest_text'], "empty collection manifest_text is not empty"
717   end
718
719   [['can_write', 'can_read', 'can_write'],
720    ['can_manage', 'can_write', 'can_manage'],
721    ['can_manage', 'can_read', 'can_manage'],
722    ['can_read', 'can_write', 'can_write'],
723    ['can_read', 'can_manage', 'can_manage'],
724    ['can_write', 'can_manage', 'can_manage'],
725   ].each do |perm1, perm2, expect|
726     test "creating #{perm2} permission returns existing #{perm1} link as #{expect}" do
727       link1 = act_as_system_user do
728         Link.create!({
729                        link_class: "permission",
730                        tail_uuid: users(:active).uuid,
731                        head_uuid: collections(:baz_file).uuid,
732                        name: perm1,
733                      })
734       end
735       post "/arvados/v1/links",
736            params: {
737              link: {
738                link_class: "permission",
739                tail_uuid: users(:active).uuid,
740                head_uuid: collections(:baz_file).uuid,
741                name: perm2,
742              },
743            },
744            headers: auth(:admin)
745       assert_response :success
746       assert_equal link1.uuid, json_response["uuid"]
747       assert_equal expect, json_response["name"]
748       link1.reload
749       assert_equal expect, link1.name
750     end
751   end
752
753   test "creating duplicate login permission returns existing link" do
754     link1 = act_as_system_user do
755       Link.create!({
756                      link_class: "permission",
757                      tail_uuid: users(:active).uuid,
758                      head_uuid: virtual_machines(:testvm2).uuid,
759                      name: "can_login",
760                      properties: {"username": "foo1"}
761                    })
762     end
763     link2 = act_as_system_user do
764       Link.create!({
765                      link_class: "permission",
766                      tail_uuid: users(:active).uuid,
767                      head_uuid: virtual_machines(:testvm2).uuid,
768                      name: "can_login",
769                      properties: {"username": "foo2"}
770                    })
771     end
772     link3 = act_as_system_user do
773       Link.create!({
774                      link_class: "permission",
775                      tail_uuid: users(:active).uuid,
776                      head_uuid: virtual_machines(:testvm2).uuid,
777                      name: "can_read",
778                    })
779     end
780     post "/arvados/v1/links",
781          params: {
782            link: {
783              link_class: "permission",
784              tail_uuid: users(:active).uuid,
785              head_uuid: virtual_machines(:testvm2).uuid,
786              name: "can_login",
787              properties: {"username": "foo2"},
788            },
789          },
790          headers: auth(:admin)
791     assert_response :success
792     assert_equal link2.uuid, json_response["uuid"]
793     assert_equal link2.created_at.to_date, json_response["created_at"].to_date
794     assert_equal "can_login", json_response["name"]
795     assert_equal "foo2", json_response["properties"]["username"]
796     link1.reload
797     assert_equal "foo1", link1.properties["username"]
798     link2.reload
799     assert_equal "foo2", link2.properties["username"]
800   end
801 end