21700: Install Bundler system-wide in Rails postinst
[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 specimens, RW group-admin can update" do
306     [[:rominiadmin, false],
307      [:miniadmin, true]].each do |which_user, update_should_succeed|
308       get "/arvados/v1/specimens",
309         params: {:format => :json},
310         headers: auth(which_user)
311       assert_response :success
312       resp_uuids = json_response['items'].collect { |i| i['uuid'] }
313       [[true, specimens(:owned_by_active_user).uuid],
314        [true, specimens(:owned_by_private_group).uuid],
315        [false, specimens(:owned_by_spectator).uuid],
316       ].each do |should_find, uuid|
317         assert_equal(should_find, !resp_uuids.index(uuid).nil?,
318                      "%s should%s see %s in specimen list" %
319                      [which_user.to_s,
320                       should_find ? '' : 'not ',
321                       uuid])
322         put "/arvados/v1/specimens/#{uuid}",
323           params: {
324             :specimen => {
325               properties: {
326                 miniadmin_was_here: true
327               }
328             },
329             :format => :json
330           },
331           headers: auth(which_user)
332         if !should_find
333           assert_response 404
334         elsif !update_should_succeed
335           assert_response 403
336         else
337           assert_response :success
338         end
339       end
340     end
341   end
342
343   test "get_permissions returns list" do
344     # First confirm that user :active cannot get permissions on group :public
345     get "/arvados/v1/permissions/#{groups(:public).uuid}",
346       params: nil,
347       headers: auth(:active)
348     assert_response 404
349
350     get "/arvados/v1/links",
351         params: {
352           :filters => [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
353         },
354       headers: auth(:active)
355     assert_response :success
356     assert_equal [], json_response['items']
357
358     ### add some permissions, including can_manage
359     ### permission for user :active
360     post "/arvados/v1/links",
361       params: {
362         :format => :json,
363         :link => {
364           tail_uuid: users(:spectator).uuid,
365           link_class: 'permission',
366           name: 'can_read',
367           head_uuid: groups(:public).uuid,
368           properties: {}
369         }
370       },
371       headers: auth(:admin)
372     assert_response :success
373     can_read_uuid = json_response['uuid']
374
375     post "/arvados/v1/links",
376       params: {
377         :format => :json,
378         :link => {
379           tail_uuid: users(:inactive).uuid,
380           link_class: 'permission',
381           name: 'can_write',
382           head_uuid: groups(:public).uuid,
383           properties: {}
384         }
385       },
386       headers: auth(:admin)
387     assert_response :success
388     can_write_uuid = json_response['uuid']
389
390     # Still should not be able read these permission links
391     get "/arvados/v1/permissions/#{groups(:public).uuid}",
392       params: nil,
393       headers: auth(:active)
394     assert_response 404
395
396     get "/arvados/v1/links",
397         params: {
398           :filters => [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
399         },
400       headers: auth(:active)
401     assert_response :success
402     assert_equal [], json_response['items']
403
404     # Shouldn't be able to read links directly either
405     get "/arvados/v1/links/#{can_read_uuid}",
406         params: {},
407       headers: auth(:active)
408     assert_response 404
409
410     ### Now add a can_manage link
411     post "/arvados/v1/links",
412       params: {
413         :format => :json,
414         :link => {
415           tail_uuid: users(:active).uuid,
416           link_class: 'permission',
417           name: 'can_manage',
418           head_uuid: groups(:public).uuid,
419           properties: {}
420         }
421       },
422       headers: auth(:admin)
423     assert_response :success
424     can_manage_uuid = json_response['uuid']
425
426     # user :active should be able to retrieve permissions
427     # on group :public using get_permissions
428     get("/arvados/v1/permissions/#{groups(:public).uuid}",
429       params: { :format => :json },
430       headers: auth(:active))
431     assert_response :success
432
433     perm_uuids = json_response['items'].map { |item| item['uuid'] }
434     assert_includes perm_uuids, can_read_uuid, "can_read_uuid not found"
435     assert_includes perm_uuids, can_write_uuid, "can_write_uuid not found"
436     assert_includes perm_uuids, can_manage_uuid, "can_manage_uuid not found"
437
438     # user :active should be able to retrieve permissions
439     # on group :public using link list
440     get "/arvados/v1/links",
441         params: {
442           :filters => [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
443         },
444       headers: auth(:active)
445     assert_response :success
446
447     perm_uuids = json_response['items'].map { |item| item['uuid'] }
448     assert_includes perm_uuids, can_read_uuid, "can_read_uuid not found"
449     assert_includes perm_uuids, can_write_uuid, "can_write_uuid not found"
450     assert_includes perm_uuids, can_manage_uuid, "can_manage_uuid not found"
451
452     # Should be able to read links directly too
453     get "/arvados/v1/links/#{can_read_uuid}",
454       headers: auth(:active)
455     assert_response :success
456
457     ### Create some objects of different types (other than projects)
458     ### inside a subproject inside the shared project, and share those
459     ### individual objects with a 3rd user ("spectator").
460     post '/arvados/v1/groups',
461          params: {
462            group: {
463              owner_uuid: groups(:public).uuid,
464              name: 'permission test subproject',
465              group_class: 'project',
466            },
467          },
468          headers: auth(:admin)
469     assert_response :success
470     subproject_uuid = json_response['uuid']
471
472     test_types = ['collection', 'workflow', 'container_request']
473     test_type_create_attrs = {
474       'container_request' => {
475         command: ["echo", "foo"],
476         container_image: links(:docker_image_collection_tag).name,
477         cwd: "/tmp",
478         environment: {},
479         mounts: {"/out" => {kind: "tmp", capacity: 1000000}},
480         output_path: "/out",
481         runtime_constraints: {"vcpus" => 1, "ram" => 2},
482       },
483     }
484
485     test_object = {}
486     test_object_perm_link = {}
487     test_types.each do |test_type|
488       post "/arvados/v1/#{test_type}s",
489            params: {
490              test_type.to_sym => {
491                owner_uuid: subproject_uuid,
492                name: "permission test #{test_type} in subproject",
493              }.merge(test_type_create_attrs[test_type] || {}).to_json,
494            },
495            headers: auth(:admin)
496       assert_response :success
497       test_object[test_type] = json_response
498
499       post '/arvados/v1/links',
500            params: {
501              link: {
502                tail_uuid: users(:spectator).uuid,
503                link_class: 'permission',
504                name: 'can_read',
505                head_uuid: test_object[test_type]['uuid'],
506              }
507            },
508            headers: auth(:admin)
509       assert_response :success
510       test_object_perm_link[test_type] = json_response
511     end
512
513     # The "active-can_manage-project" permission should cause the
514     # "spectator-can_read-object" links to be visible to the "active"
515     # user.
516     test_types.each do |test_type|
517       get "/arvados/v1/permissions/#{test_object[test_type]['uuid']}",
518           headers: auth(:active)
519       assert_response :success
520       perm_uuids = json_response['items'].map { |item| item['uuid'] }
521       assert_includes perm_uuids, test_object_perm_link[test_type]['uuid'], "can_read_uuid not found"
522
523       get "/arvados/v1/links/#{test_object_perm_link[test_type]['uuid']}",
524           headers: auth(:active)
525       assert_response :success
526
527       [
528         ['head_uuid', '=', test_object[test_type]['uuid']],
529         ['head_uuid', 'in', [test_object[test_type]['uuid']]],
530         ['head_uuid', 'in', [users(:admin).uuid, test_object[test_type]['uuid']]],
531       ].each do |filter|
532         get "/arvados/v1/links",
533             params: {
534               filters: ([['link_class', '=', 'permission'], filter]).to_json,
535             },
536             headers: auth(:active)
537         assert_response :success
538         assert_not_empty json_response['items'], "could not find can_read link using index with filter #{filter}"
539         assert_equal test_object_perm_link[test_type]['uuid'], json_response['items'][0]['uuid']
540       end
541
542       # The "spectator-can_read-object" link should be visible to the
543       # subject user ("spectator") in a filter query, even without
544       # can_manage permission on the target object.
545       [
546         ['tail_uuid', '=', users(:spectator).uuid],
547       ].each do |filter|
548         get "/arvados/v1/links",
549             params: {
550               filters: ([['link_class', '=', 'permission'], filter]).to_json,
551             },
552             headers: auth(:spectator)
553         assert_response :success
554         perm_uuids = json_response['items'].map { |item| item['uuid'] }
555         assert_includes perm_uuids, test_object_perm_link[test_type]['uuid'], "could not find can_read link using index with filter #{filter}"
556       end
557     end
558
559     ### Now delete the can_manage link
560     delete "/arvados/v1/links/#{can_manage_uuid}",
561       headers: auth(:active)
562     assert_response :success
563
564     # Should not be able read these permission links again
565     test_types.each do |test_type|
566       get "/arvados/v1/permissions/#{groups(:public).uuid}",
567           headers: auth(:active)
568       assert_response 404
569
570       get "/arvados/v1/permissions/#{test_object[test_type]['uuid']}",
571           headers: auth(:active)
572       assert_response 404
573
574       get "/arvados/v1/links",
575           params: {
576             filters: [["link_class", "=", "permission"], ["head_uuid", "=", groups(:public).uuid]].to_json
577           },
578           headers: auth(:active)
579       assert_response :success
580       assert_equal [], json_response['items']
581
582       [
583         ['head_uuid', '=', test_object[test_type]['uuid']],
584         ['head_uuid', 'in', [users(:admin).uuid, test_object[test_type]['uuid']]],
585         ['head_uuid', 'in', []],
586       ].each do |filter|
587         get "/arvados/v1/links",
588             params: {
589               :filters => [["link_class", "=", "permission"], filter].to_json
590             },
591             headers: auth(:active)
592         assert_response :success
593         assert_equal [], json_response['items']
594       end
595
596       # Should not be able to read links directly either
597       get "/arvados/v1/links/#{can_read_uuid}",
598           headers: auth(:active)
599       assert_response 404
600
601       test_types.each do |test_type|
602         get "/arvados/v1/links/#{test_object_perm_link[test_type]['uuid']}",
603             headers: auth(:active)
604         assert_response 404
605       end
606     end
607
608     ### Create a collection, and share it with a direct permission
609     ### link (as opposed to sharing its parent project)
610     post "/arvados/v1/collections",
611       params: {
612         collection: {
613           name: 'permission test',
614         }
615       },
616       headers: auth(:admin)
617     assert_response :success
618     collection_uuid = json_response['uuid']
619     post "/arvados/v1/links",
620       params: {
621         link: {
622           tail_uuid: users(:spectator).uuid,
623           link_class: 'permission',
624           name: 'can_read',
625           head_uuid: collection_uuid,
626           properties: {}
627         }
628       },
629       headers: auth(:admin)
630     assert_response :success
631     can_read_collection_uuid = json_response['uuid']
632
633     # Should not be able read the permission link via permissions API,
634     # because permission is only can_read, not can_manage
635     get "/arvados/v1/permissions/#{collection_uuid}",
636       headers: auth(:active)
637     assert_response 404
638
639     # Should not be able to read the permission link directly, for
640     # same reason
641     get "/arvados/v1/links/#{can_read_collection_uuid}",
642       headers: auth(:active)
643     assert_response 404
644
645     ### Now add a can_manage link
646     post "/arvados/v1/links",
647       params: {
648         link: {
649           tail_uuid: users(:active).uuid,
650           link_class: 'permission',
651           name: 'can_manage',
652           head_uuid: collection_uuid,
653           properties: {}
654         }
655       },
656       headers: auth(:admin)
657     assert_response :success
658     can_manage_collection_uuid = json_response['uuid']
659
660     # Should be able read both permission links via permissions API
661     get "/arvados/v1/permissions/#{collection_uuid}",
662       headers: auth(:active)
663     assert_response :success
664     perm_uuids = json_response['items'].map { |item| item['uuid'] }
665     assert_includes perm_uuids, can_read_collection_uuid, "can_read_uuid not found"
666     assert_includes perm_uuids, can_manage_collection_uuid, "can_manage_uuid not found"
667
668     # Should be able to read both permission links directly
669     [can_read_collection_uuid, can_manage_collection_uuid].each do |uuid|
670       get "/arvados/v1/links/#{uuid}",
671         headers: auth(:active)
672       assert_response :success
673     end
674   end
675
676   test "get_permissions returns 404 for nonexistent uuid" do
677     nonexistent = Group.generate_uuid
678     # make sure it really doesn't exist
679     get "/arvados/v1/groups/#{nonexistent}", params: nil, headers: auth(:admin)
680     assert_response 404
681
682     get "/arvados/v1/permissions/#{nonexistent}", params: nil, headers: auth(:active)
683     assert_response 404
684   end
685
686   test "get_permissions returns 403 if user can read but not manage" do
687     post "/arvados/v1/links",
688       params: {
689         :link => {
690           tail_uuid: users(:active).uuid,
691           link_class: 'permission',
692           name: 'can_read',
693           head_uuid: groups(:public).uuid,
694           properties: {}
695         }
696       },
697       headers: auth(:admin)
698     assert_response :success
699
700     get "/arvados/v1/permissions/#{groups(:public).uuid}",
701       params: nil,
702       headers: auth(:active)
703     assert_response 403
704   end
705
706   test "active user can read the empty collection" do
707     # The active user should be able to read the empty collection.
708
709     get("/arvados/v1/collections/#{empty_collection_pdh}",
710       params: {:format => :json},
711       headers: auth(:active))
712     assert_response :success
713     assert_empty json_response['manifest_text'], "empty collection manifest_text is not empty"
714   end
715
716   [['can_write', 'can_read', 'can_write'],
717    ['can_manage', 'can_write', 'can_manage'],
718    ['can_manage', 'can_read', 'can_manage'],
719    ['can_read', 'can_write', 'can_write'],
720    ['can_read', 'can_manage', 'can_manage'],
721    ['can_write', 'can_manage', 'can_manage'],
722   ].each do |perm1, perm2, expect|
723     test "creating #{perm2} permission returns existing #{perm1} link as #{expect}" do
724       link1 = act_as_system_user do
725         Link.create!({
726                        link_class: "permission",
727                        tail_uuid: users(:active).uuid,
728                        head_uuid: collections(:baz_file).uuid,
729                        name: perm1,
730                      })
731       end
732       post "/arvados/v1/links",
733            params: {
734              link: {
735                link_class: "permission",
736                tail_uuid: users(:active).uuid,
737                head_uuid: collections(:baz_file).uuid,
738                name: perm2,
739              },
740            },
741            headers: auth(:admin)
742       assert_response :success
743       assert_equal link1.uuid, json_response["uuid"]
744       assert_equal expect, json_response["name"]
745       link1.reload
746       assert_equal expect, link1.name
747     end
748   end
749
750   test "creating duplicate login permission returns existing link" do
751     link1 = act_as_system_user do
752       Link.create!({
753                      link_class: "permission",
754                      tail_uuid: users(:active).uuid,
755                      head_uuid: virtual_machines(:testvm2).uuid,
756                      name: "can_login",
757                      properties: {"username": "foo1"}
758                    })
759     end
760     link2 = act_as_system_user do
761       Link.create!({
762                      link_class: "permission",
763                      tail_uuid: users(:active).uuid,
764                      head_uuid: virtual_machines(:testvm2).uuid,
765                      name: "can_login",
766                      properties: {"username": "foo2"}
767                    })
768     end
769     link3 = act_as_system_user do
770       Link.create!({
771                      link_class: "permission",
772                      tail_uuid: users(:active).uuid,
773                      head_uuid: virtual_machines(:testvm2).uuid,
774                      name: "can_read",
775                    })
776     end
777     post "/arvados/v1/links",
778          params: {
779            link: {
780              link_class: "permission",
781              tail_uuid: users(:active).uuid,
782              head_uuid: virtual_machines(:testvm2).uuid,
783              name: "can_login",
784              properties: {"username": "foo2"},
785            },
786          },
787          headers: auth(:admin)
788     assert_response :success
789     assert_equal link2.uuid, json_response["uuid"]
790     assert_equal link2.created_at.to_date, json_response["created_at"].to_date
791     assert_equal "can_login", json_response["name"]
792     assert_equal "foo2", json_response["properties"]["username"]
793     link1.reload
794     assert_equal "foo1", link1.properties["username"]
795     link2.reload
796     assert_equal "foo2", link2.properties["username"]
797   end
798 end