1 # Copyright (C) The Arvados Authors. All rights reserved.
3 # SPDX-License-Identifier: AGPL-3.0
7 class Arvados::V1::CollectionsControllerTest < ActionController::TestCase
10 PERM_TOKEN_RE = /\+A[[:xdigit:]]+@[[:xdigit:]]{8}\b/
12 def permit_unsigned_manifests isok=true
13 # Set security model for the life of a test.
14 Rails.configuration.permit_create_collection_with_unsigned_manifest = isok
17 def assert_signed_manifest manifest_text, label=''
18 assert_not_nil manifest_text, "#{label} manifest_text was nil"
19 manifest_text.scan(/ [[:xdigit:]]{32}\S*/) do |tok|
20 assert_match(PERM_TOKEN_RE, tok,
21 "Locator in #{label} manifest_text was not signed")
25 def assert_unsigned_manifest resp, label=''
26 txt = resp['unsigned_manifest_text']
27 assert_not_nil(txt, "#{label} unsigned_manifest_text was nil")
29 txt.scan(/ [[:xdigit:]]{32}\S*/) do |tok|
31 refute_match(PERM_TOKEN_RE, tok,
32 "Locator in #{label} unsigned_manifest_text was signed: #{tok}")
37 test "should get index" do
38 authorize_with :active
40 assert_response :success
41 assert(assigns(:objects).andand.any?, "no Collections returned in index")
42 refute(json_response["items"].any? { |c| c.has_key?("manifest_text") },
43 "basic Collections index included manifest_text")
44 refute(json_response["items"].any? { |c| c["uuid"] == collections(:collection_owned_by_active_past_version_1).uuid },
45 "basic Collections index included past version")
48 test "get index with include_old_versions" do
49 authorize_with :active
51 include_old_versions: true
53 assert_response :success
54 assert(assigns(:objects).andand.any?, "no Collections returned in index")
55 assert(json_response["items"].any? { |c| c["uuid"] == collections(:collection_owned_by_active_past_version_1).uuid },
56 "past version not included on index")
59 test "collections.get returns signed locators, and no unsigned_manifest_text" do
60 permit_unsigned_manifests
61 authorize_with :active
62 get :show, {id: collections(:foo_file).uuid}
63 assert_response :success
64 assert_signed_manifest json_response['manifest_text'], 'foo_file'
65 refute_includes json_response, 'unsigned_manifest_text'
68 test "index with manifest_text selected returns signed locators" do
69 columns = %w(uuid owner_uuid manifest_text)
70 authorize_with :active
71 get :index, select: columns
72 assert_response :success
73 assert(assigns(:objects).andand.any?,
74 "no Collections returned for index with columns selected")
75 json_response["items"].each do |coll|
76 assert_equal(coll.keys - ['kind'], columns,
77 "Collections index did not respect selected columns")
78 assert_signed_manifest coll['manifest_text'], coll['uuid']
82 test "index with unsigned_manifest_text selected returns only unsigned locators" do
83 authorize_with :active
84 get :index, select: ['unsigned_manifest_text']
85 assert_response :success
86 assert_operator json_response["items"].count, :>, 0
88 json_response["items"].each do |coll|
89 assert_equal(coll.keys - ['kind'], ['unsigned_manifest_text'],
90 "Collections index did not respect selected columns")
91 locs += assert_unsigned_manifest coll, coll['uuid']
93 assert_operator locs, :>, 0, "no locators found in any manifests"
96 test 'index without select returns everything except manifest' do
97 authorize_with :active
99 assert_response :success
100 assert json_response['items'].any?
101 json_response['items'].each do |coll|
102 assert_includes(coll.keys, 'uuid')
103 assert_includes(coll.keys, 'name')
104 assert_includes(coll.keys, 'created_at')
105 refute_includes(coll.keys, 'manifest_text')
109 ['', nil, false, 'null'].each do |select|
110 test "index with select=#{select.inspect} returns everything except manifest" do
111 authorize_with :active
112 get :index, select: select
113 assert_response :success
114 assert json_response['items'].any?
115 json_response['items'].each do |coll|
116 assert_includes(coll.keys, 'uuid')
117 assert_includes(coll.keys, 'name')
118 assert_includes(coll.keys, 'created_at')
119 refute_includes(coll.keys, 'manifest_text')
125 ["uuid", "manifest_text"],
127 '["uuid", "manifest_text"]'].each do |select|
128 test "index with select=#{select.inspect} returns no name" do
129 authorize_with :active
130 get :index, select: select
131 assert_response :success
132 assert json_response['items'].any?
133 json_response['items'].each do |coll|
134 refute_includes(coll.keys, 'name')
139 [0,1,2].each do |limit|
140 test "get index with limit=#{limit}" do
141 authorize_with :active
142 get :index, limit: limit
143 assert_response :success
144 assert_equal limit, assigns(:objects).count
145 resp = JSON.parse(@response.body)
146 assert_equal limit, resp['limit']
150 test "items.count == items_available" do
151 authorize_with :active
152 get :index, limit: 100000
153 assert_response :success
154 resp = JSON.parse(@response.body)
155 assert_equal resp['items_available'], assigns(:objects).length
156 assert_equal resp['items_available'], resp['items'].count
157 unique_uuids = resp['items'].collect { |i| i['uuid'] }.compact.uniq
158 assert_equal unique_uuids.count, resp['items'].count
161 test "items.count == items_available with filters" do
162 authorize_with :active
165 filters: [['uuid','=',collections(:foo_file).uuid]]
167 assert_response :success
168 assert_equal 1, assigns(:objects).length
169 assert_equal 1, json_response['items_available']
170 assert_equal 1, json_response['items'].count
173 test "get index with limit=2 offset=99999" do
174 # Assume there are not that many test fixtures.
175 authorize_with :active
176 get :index, limit: 2, offset: 99999
177 assert_response :success
178 assert_equal 0, assigns(:objects).count
179 resp = JSON.parse(@response.body)
180 assert_equal 2, resp['limit']
181 assert_equal 99999, resp['offset']
184 def request_capped_index(params={})
185 authorize_with :user1_with_load
186 coll1 = collections(:collection_1_of_201)
187 Rails.configuration.max_index_database_read =
188 yield(coll1.manifest_text.size)
190 select: %w(uuid manifest_text),
191 filters: [["owner_uuid", "=", coll1.owner_uuid]],
196 test "index with manifest_text limited by max_index_database_read returns non-empty" do
197 request_capped_index() { |_| 1 }
198 assert_response :success
199 assert_equal(1, json_response["items"].size)
200 assert_equal(1, json_response["limit"])
201 assert_equal(201, json_response["items_available"])
204 test "max_index_database_read size check follows same order as real query" do
205 authorize_with :user1_with_load
206 txt = '.' + ' d41d8cd98f00b204e9800998ecf8427e+0'*1000 + " 0:0:empty.txt\n"
207 c = Collection.create! manifest_text: txt, name: '0000000000000000000'
208 request_capped_index(select: %w(uuid manifest_text name),
210 filters: [['name','>=',c.name]]) do |_|
213 assert_response :success
214 assert_equal(1, json_response["items"].size)
215 assert_equal(1, json_response["limit"])
216 assert_equal(c.uuid, json_response["items"][0]["uuid"])
217 # The effectiveness of the test depends on >1 item matching the filters.
218 assert_operator(1, :<, json_response["items_available"])
221 test "index with manifest_text limited by max_index_database_read" do
222 request_capped_index() { |size| (size * 3) + 1 }
223 assert_response :success
224 assert_equal(3, json_response["items"].size)
225 assert_equal(3, json_response["limit"])
226 assert_equal(201, json_response["items_available"])
229 test "max_index_database_read does not interfere with limit" do
230 request_capped_index(limit: 5) { |size| size * 20 }
231 assert_response :success
232 assert_equal(5, json_response["items"].size)
233 assert_equal(5, json_response["limit"])
234 assert_equal(201, json_response["items_available"])
237 test "max_index_database_read does not interfere with order" do
238 request_capped_index(select: %w(uuid manifest_text name),
239 order: "name DESC") { |size| (size * 11) + 1 }
240 assert_response :success
241 assert_equal(11, json_response["items"].size)
242 assert_empty(json_response["items"].reject do |coll|
243 coll["name"] =~ /^Collection_9/
245 assert_equal(11, json_response["limit"])
246 assert_equal(201, json_response["items_available"])
249 test "admin can create collection with unsigned manifest" do
250 authorize_with :admin
252 manifest_text: <<-EOS
253 . d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt
254 . acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
255 . acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
256 ./baz acbd18db4cc2f85cedef654fccc4a4d8+3 0:3:bar.txt
259 test_collection[:portable_data_hash] =
260 Digest::MD5.hexdigest(test_collection[:manifest_text]) +
262 test_collection[:manifest_text].length.to_s
264 # post :create will modify test_collection in place, so we save a copy first.
265 # Hash.deep_dup is not sufficient as it preserves references of strings (??!?)
266 post_collection = Marshal.load(Marshal.dump(test_collection))
268 collection: post_collection
271 assert_response :success
272 assert_nil assigns(:objects)
274 response_collection = assigns(:object)
276 stored_collection = Collection.select([:uuid, :portable_data_hash, :manifest_text]).
277 where(portable_data_hash: response_collection['portable_data_hash']).first
279 assert_equal test_collection[:portable_data_hash], stored_collection['portable_data_hash']
281 # The manifest in the response will have had permission hints added.
282 # Remove any permission hints in the response before comparing it to the source.
283 stripped_manifest = stored_collection['manifest_text'].gsub(/\+A[A-Za-z0-9@_-]+/, '')
284 assert_equal test_collection[:manifest_text], stripped_manifest
286 # TBD: create action should add permission signatures to manifest_text in the response,
287 # and we need to check those permission signatures here.
290 [:admin, :active].each do |user|
291 test "#{user} can get collection using portable data hash" do
294 foo_collection = collections(:foo_file)
296 # Get foo_file using its portable data hash
298 id: foo_collection[:portable_data_hash]
300 assert_response :success
301 assert_not_nil assigns(:object)
302 resp = assigns(:object)
303 assert_equal foo_collection[:portable_data_hash], resp[:portable_data_hash]
304 assert_signed_manifest resp[:manifest_text]
306 # The manifest in the response will have had permission hints added.
307 # Remove any permission hints in the response before comparing it to the source.
308 stripped_manifest = resp[:manifest_text].gsub(/\+A[A-Za-z0-9@_-]+/, '')
309 assert_equal foo_collection[:manifest_text], stripped_manifest
313 test "create with owner_uuid set to owned group" do
314 permit_unsigned_manifests
315 authorize_with :active
316 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
319 owner_uuid: 'zzzzz-j7d0g-rew6elm53kancon',
320 manifest_text: manifest_text,
321 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
324 assert_response :success
325 resp = JSON.parse(@response.body)
326 assert_equal 'zzzzz-j7d0g-rew6elm53kancon', resp['owner_uuid']
329 test "create fails with duplicate name" do
330 permit_unsigned_manifests
331 authorize_with :admin
332 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
335 owner_uuid: 'zzzzz-tpzed-000000000000000',
336 manifest_text: manifest_text,
337 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47",
342 response_errors = json_response['errors']
343 assert_not_nil response_errors, 'Expected error in response'
344 assert(response_errors.first.include?('duplicate key'),
345 "Expected 'duplicate key' error in #{response_errors.first}")
348 [false, true].each do |unsigned|
349 test "create with duplicate name, ensure_unique_name, unsigned=#{unsigned}" do
350 permit_unsigned_manifests unsigned
351 authorize_with :active
352 manifest_text = ". acbd18db4cc2f85cedef654fccc4a4d8+3 0:0:foo.txt\n"
354 manifest_text = Collection.sign_manifest manifest_text, api_token(:active)
358 owner_uuid: users(:active).uuid,
359 manifest_text: manifest_text,
360 name: "owned_by_active"
362 ensure_unique_name: true
364 assert_response :success
365 assert_match /^owned_by_active \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
369 test "create with owner_uuid set to group i can_manage" do
370 permit_unsigned_manifests
371 authorize_with :active
372 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
375 owner_uuid: groups(:active_user_has_can_manage).uuid,
376 manifest_text: manifest_text,
377 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
380 assert_response :success
381 resp = JSON.parse(@response.body)
382 assert_equal groups(:active_user_has_can_manage).uuid, resp['owner_uuid']
385 test "create with owner_uuid fails on group with only can_read permission" do
386 permit_unsigned_manifests
387 authorize_with :active
388 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
391 owner_uuid: groups(:all_users).uuid,
392 manifest_text: manifest_text,
393 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
399 test "create with owner_uuid fails on group with no permission" do
400 permit_unsigned_manifests
401 authorize_with :active
402 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
405 owner_uuid: groups(:public).uuid,
406 manifest_text: manifest_text,
407 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
413 test "admin create with owner_uuid set to group with no permission" do
414 permit_unsigned_manifests
415 authorize_with :admin
416 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
419 owner_uuid: 'zzzzz-j7d0g-it30l961gq3t0oi',
420 manifest_text: manifest_text,
421 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
424 assert_response :success
427 test "should create with collection passed as json" do
428 permit_unsigned_manifests
429 authorize_with :active
433 "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",\
434 "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
438 assert_response :success
441 test "should fail to create with checksum mismatch" do
442 permit_unsigned_manifests
443 authorize_with :active
447 "manifest_text":". d41d8cd98f00b204e9800998ecf8427e 0:0:bar.txt\n",\
448 "portable_data_hash":"d30fe8ae534397864cb96c544f4cf102+47"\
455 test "collection UUID is normalized when created" do
456 permit_unsigned_manifests
457 authorize_with :active
460 manifest_text: ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",
461 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47+Khint+Xhint+Zhint"
464 assert_response :success
465 assert_not_nil assigns(:object)
466 resp = JSON.parse(@response.body)
467 assert_equal "d30fe8ae534397864cb96c544f4cf102+47", resp['portable_data_hash']
470 test "get full provenance for baz file" do
471 authorize_with :active
472 get :provenance, id: 'ea10d51bcf88862dbcc36eb292017dfd+45'
473 assert_response :success
474 resp = JSON.parse(@response.body)
475 assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
476 assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
477 assert_not_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
478 assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq'] # bar->baz
479 assert_not_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon'] # foo->bar
482 test "get no provenance for foo file" do
483 # spectator user cannot even see baz collection
484 authorize_with :spectator
485 get :provenance, id: '1f4b0bc7583c2a7f9102c395f4ffc5e3+45'
489 test "get partial provenance for baz file" do
490 # spectator user can see bar->baz job, but not foo->bar job
491 authorize_with :spectator
492 get :provenance, id: 'ea10d51bcf88862dbcc36eb292017dfd+45'
493 assert_response :success
494 resp = JSON.parse(@response.body)
495 assert_not_nil resp['ea10d51bcf88862dbcc36eb292017dfd+45'] # baz
496 assert_not_nil resp['fa7aeb5140e2848d39b416daeef4ffc5+45'] # bar
497 assert_not_nil resp['zzzzz-8i9sb-cjs4pklxxjykyuq'] # bar->baz
498 assert_nil resp['zzzzz-8i9sb-aceg2bnq7jt7kon'] # foo->bar
499 assert_nil resp['1f4b0bc7583c2a7f9102c395f4ffc5e3+45'] # foo
502 test "search collections with 'any' operator" do
503 expect_pdh = collections(:docker_image).portable_data_hash
504 authorize_with :active
506 where: { any: ['contains', expect_pdh[5..25]] }
508 assert_response :success
509 found = assigns(:objects)
510 assert_equal 1, found.count
511 assert_equal expect_pdh, found.first.portable_data_hash
514 [false, true].each do |permit_unsigned|
515 test "create collection with signed manifest, permit_unsigned=#{permit_unsigned}" do
516 permit_unsigned_manifests permit_unsigned
517 authorize_with :active
519 d41d8cd98f00b204e9800998ecf8427e+0
520 acbd18db4cc2f85cedef654fccc4a4d8+3
521 ea10d51bcf88862dbcc36eb292017dfd+45)
523 unsigned_manifest = locators.map { |loc|
524 ". " + loc + " 0:0:foo.txt\n"
526 manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
528 unsigned_manifest.length.to_s
530 # Build a manifest with both signed and unsigned locators.
532 key: Rails.configuration.blob_signing_key,
533 api_token: api_token(:active),
535 signed_locators = locators.collect do |x|
536 Blob.sign_locator x, signing_opts
539 # Leave a non-empty blob unsigned.
540 signed_locators[1] = locators[1]
542 # Leave the empty blob unsigned. This should still be allowed.
543 signed_locators[0] = locators[0]
546 ". " + signed_locators[0] + " 0:0:foo.txt\n" +
547 ". " + signed_locators[1] + " 0:0:foo.txt\n" +
548 ". " + signed_locators[2] + " 0:0:foo.txt\n"
552 manifest_text: signed_manifest,
553 portable_data_hash: manifest_uuid,
556 assert_response :success
557 assert_not_nil assigns(:object)
558 resp = JSON.parse(@response.body)
559 assert_equal manifest_uuid, resp['portable_data_hash']
560 # All of the locators in the output must be signed.
561 resp['manifest_text'].lines.each do |entry|
562 m = /([[:xdigit:]]{32}\+\S+)/.match(entry)
564 assert Blob.verify_signature m[0], signing_opts
570 test "create collection with signed manifest and explicit TTL" do
571 authorize_with :active
573 d41d8cd98f00b204e9800998ecf8427e+0
574 acbd18db4cc2f85cedef654fccc4a4d8+3
575 ea10d51bcf88862dbcc36eb292017dfd+45)
577 unsigned_manifest = locators.map { |loc|
578 ". " + loc + " 0:0:foo.txt\n"
580 manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
582 unsigned_manifest.length.to_s
584 # build a manifest with both signed and unsigned locators.
585 # TODO(twp): in phase 4, all locators will need to be signed, so
586 # this test should break and will need to be rewritten. Issue #2755.
588 key: Rails.configuration.blob_signing_key,
589 api_token: api_token(:active),
593 ". " + locators[0] + " 0:0:foo.txt\n" +
594 ". " + Blob.sign_locator(locators[1], signing_opts) + " 0:0:foo.txt\n" +
595 ". " + Blob.sign_locator(locators[2], signing_opts) + " 0:0:foo.txt\n"
599 manifest_text: signed_manifest,
600 portable_data_hash: manifest_uuid,
603 assert_response :success
604 assert_not_nil assigns(:object)
605 resp = JSON.parse(@response.body)
606 assert_equal manifest_uuid, resp['portable_data_hash']
607 # All of the locators in the output must be signed.
608 resp['manifest_text'].lines.each do |entry|
609 m = /([[:xdigit:]]{32}\+\S+)/.match(entry)
611 assert Blob.verify_signature m[0], signing_opts
616 test "create fails with invalid signature" do
617 authorize_with :active
619 key: Rails.configuration.blob_signing_key,
620 api_token: api_token(:active),
623 # Generate a locator with a bad signature.
624 unsigned_locator = "acbd18db4cc2f85cedef654fccc4a4d8+3"
625 bad_locator = unsigned_locator + "+Affffffffffffffffffffffffffffffffffffffff@ffffffff"
626 assert !Blob.verify_signature(bad_locator, signing_opts)
628 # Creating a collection with this locator should
629 # produce 403 Permission denied.
630 unsigned_manifest = ". #{unsigned_locator} 0:0:foo.txt\n"
631 manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
633 unsigned_manifest.length.to_s
635 bad_manifest = ". #{bad_locator} 0:0:foo.txt\n"
638 manifest_text: bad_manifest,
639 portable_data_hash: manifest_uuid
646 test "create fails with uuid of signed manifest" do
647 authorize_with :active
649 key: Rails.configuration.blob_signing_key,
650 api_token: api_token(:active),
653 unsigned_locator = "d41d8cd98f00b204e9800998ecf8427e+0"
654 signed_locator = Blob.sign_locator(unsigned_locator, signing_opts)
655 signed_manifest = ". #{signed_locator} 0:0:foo.txt\n"
656 manifest_uuid = Digest::MD5.hexdigest(signed_manifest) +
658 signed_manifest.length.to_s
662 manifest_text: signed_manifest,
663 portable_data_hash: manifest_uuid
670 test "reject manifest with unsigned block as stream name" do
671 authorize_with :active
674 manifest_text: "00000000000000000000000000000000+1234 d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n"
677 assert_includes [422, 403], response.code.to_i
680 test "multiple locators per line" do
681 permit_unsigned_manifests
682 authorize_with :active
684 d41d8cd98f00b204e9800998ecf8427e+0
685 acbd18db4cc2f85cedef654fccc4a4d8+3
686 ea10d51bcf88862dbcc36eb292017dfd+45)
688 manifest_text = [".", *locators, "0:0:foo.txt\n"].join(" ")
689 manifest_uuid = Digest::MD5.hexdigest(manifest_text) +
691 manifest_text.length.to_s
694 manifest_text: manifest_text,
695 portable_data_hash: manifest_uuid,
697 post_collection = Marshal.load(Marshal.dump(test_collection))
699 collection: post_collection
701 assert_response :success
702 assert_not_nil assigns(:object)
703 resp = JSON.parse(@response.body)
704 assert_equal manifest_uuid, resp['portable_data_hash']
706 # The manifest in the response will have had permission hints added.
707 # Remove any permission hints in the response before comparing it to the source.
708 stripped_manifest = resp['manifest_text'].gsub(/\+A[A-Za-z0-9@_-]+/, '')
709 assert_equal manifest_text, stripped_manifest
712 test "multiple signed locators per line" do
713 permit_unsigned_manifests
714 authorize_with :active
716 d41d8cd98f00b204e9800998ecf8427e+0
717 acbd18db4cc2f85cedef654fccc4a4d8+3
718 ea10d51bcf88862dbcc36eb292017dfd+45)
721 key: Rails.configuration.blob_signing_key,
722 api_token: api_token(:active),
725 unsigned_manifest = [".", *locators, "0:0:foo.txt\n"].join(" ")
726 manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest) +
728 unsigned_manifest.length.to_s
730 signed_locators = locators.map { |loc| Blob.sign_locator loc, signing_opts }
731 signed_manifest = [".", *signed_locators, "0:0:foo.txt\n"].join(" ")
735 manifest_text: signed_manifest,
736 portable_data_hash: manifest_uuid,
739 assert_response :success
740 assert_not_nil assigns(:object)
741 resp = JSON.parse(@response.body)
742 assert_equal manifest_uuid, resp['portable_data_hash']
743 # All of the locators in the output must be signed.
744 # Each line is of the form "path locator locator ... 0:0:file.txt"
745 # entry.split[1..-2] will yield just the tokens in the middle of the line
746 returned_locator_count = 0
747 resp['manifest_text'].lines.each do |entry|
748 entry.split[1..-2].each do |tok|
749 returned_locator_count += 1
750 assert Blob.verify_signature tok, signing_opts
753 assert_equal locators.count, returned_locator_count
756 test 'Reject manifest with unsigned blob' do
757 permit_unsigned_manifests false
758 authorize_with :active
759 unsigned_manifest = ". 0cc175b9c0f1b6a831c399e269772661+1 0:1:a.txt\n"
760 manifest_uuid = Digest::MD5.hexdigest(unsigned_manifest)
763 manifest_text: unsigned_manifest,
764 portable_data_hash: manifest_uuid,
768 "Creating a collection with unsigned blobs should respond 403"
769 assert_empty Collection.where('uuid like ?', manifest_uuid+'%'),
770 "Collection should not exist in database after failed create"
773 test 'List expired collection returns empty list' do
774 authorize_with :active
776 where: {name: 'expired_collection'},
778 assert_response :success
779 found = assigns(:objects)
780 assert_equal 0, found.count
783 test 'Show expired collection returns 404' do
784 authorize_with :active
786 id: 'zzzzz-4zz18-mto52zx1s7sn3ih',
791 test 'Update expired collection returns 404' do
792 authorize_with :active
794 id: 'zzzzz-4zz18-mto52zx1s7sn3ih',
796 name: "still expired"
802 test 'List collection with future expiration time succeeds' do
803 authorize_with :active
805 where: {name: 'collection_expires_in_future'},
807 found = assigns(:objects)
808 assert_equal 1, found.count
812 test 'Show collection with future expiration time succeeds' do
813 authorize_with :active
815 id: 'zzzzz-4zz18-padkqo7yb8d9i3j',
817 assert_response :success
820 test 'Update collection with future expiration time succeeds' do
821 authorize_with :active
823 id: 'zzzzz-4zz18-padkqo7yb8d9i3j',
825 name: "still not expired"
828 assert_response :success
831 test "get collection and verify that file_names is not included" do
832 authorize_with :active
833 get :show, {id: collections(:foo_file).uuid}
834 assert_response :success
835 assert_equal collections(:foo_file).uuid, json_response['uuid']
836 assert_nil json_response['file_names']
837 assert json_response['manifest_text']
843 ].each do |description_size, expected_response|
844 # Descriptions are not part of search indexes. Skip until
845 # full-text search is implemented, at which point replace with a
846 # search in description.
847 skip "create collection with description size #{description_size}
848 and expect response #{expected_response}" do
849 authorize_with :active
851 description = 'here is a collection with a very large description'
852 while description.length < description_size
853 description = description + description
856 post :create, collection: {
857 manifest_text: ". d41d8cd98f00b204e9800998ecf8427e+0 0:0:foo.txt\n",
858 description: description,
861 assert_response expected_response
865 [1, 5, nil].each do |ask|
866 test "Set replication_desired=#{ask.inspect}" do
867 Rails.configuration.default_collection_replication = 2
868 authorize_with :active
870 id: collections(:replication_undesired_unconfirmed).uuid,
872 replication_desired: ask,
875 assert_response :success
876 assert_equal ask, json_response['replication_desired']
880 test "get collection with properties" do
881 authorize_with :active
882 get :show, {id: collections(:collection_with_one_property).uuid}
883 assert_response :success
884 assert_not_nil json_response['uuid']
885 assert_equal 'value1', json_response['properties']['property1']
888 test "create collection with properties" do
889 authorize_with :active
890 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
893 manifest_text: manifest_text,
894 portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47",
895 properties: {'property_1' => 'value_1'}
898 assert_response :success
899 assert_not_nil json_response['uuid']
900 assert_equal 'value_1', json_response['properties']['property_1']
905 ". d41d8cd98f00b204e9800998ecf8427e foo.txt",
906 "d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
907 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
908 ].each do |manifest_text|
909 test "create collection with invalid manifest #{manifest_text} and expect error" do
910 authorize_with :active
913 manifest_text: manifest_text,
914 portable_data_hash: "d41d8cd98f00b204e9800998ecf8427e+0"
918 response_errors = json_response['errors']
919 assert_not_nil response_errors, 'Expected error in response'
920 assert(response_errors.first.include?('Invalid manifest'),
921 "Expected 'Invalid manifest' error in #{response_errors.first}")
926 [nil, "d41d8cd98f00b204e9800998ecf8427e+0"],
927 ["", "d41d8cd98f00b204e9800998ecf8427e+0"],
928 [". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n", "d30fe8ae534397864cb96c544f4cf102+47"],
929 ].each do |manifest_text, pdh|
930 test "create collection with valid manifest #{manifest_text.inspect} and expect success" do
931 authorize_with :active
934 manifest_text: manifest_text,
935 portable_data_hash: pdh
944 ". d41d8cd98f00b204e9800998ecf8427e foo.txt",
945 "d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
946 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt",
947 ].each do |manifest_text|
948 test "update collection with invalid manifest #{manifest_text} and expect error" do
949 authorize_with :active
951 id: 'zzzzz-4zz18-bv31uwvy3neko21',
953 manifest_text: manifest_text,
957 response_errors = json_response['errors']
958 assert_not_nil response_errors, 'Expected error in response'
959 assert(response_errors.first.include?('Invalid manifest'),
960 "Expected 'Invalid manifest' error in #{response_errors.first}")
967 ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n",
968 ].each do |manifest_text|
969 test "update collection with valid manifest #{manifest_text.inspect} and expect success" do
970 authorize_with :active
972 id: 'zzzzz-4zz18-bv31uwvy3neko21',
974 manifest_text: manifest_text,
981 test 'get trashed collection with include_trash' do
982 uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
983 authorize_with :active
991 test 'get trashed collection without include_trash' do
992 uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
993 authorize_with :active
1000 test 'trash collection using http DELETE verb' do
1001 uuid = collections(:collection_owned_by_active).uuid
1002 authorize_with :active
1007 c = Collection.find_by_uuid(uuid)
1008 assert_operator c.trash_at, :<, db_current_time
1009 assert_equal c.delete_at, c.trash_at + Rails.configuration.blob_signature_ttl
1012 test 'delete long-trashed collection immediately using http DELETE verb' do
1013 uuid = 'zzzzz-4zz18-mto52zx1s7sn3ih' # expired_collection
1014 authorize_with :active
1019 c = Collection.find_by_uuid(uuid)
1020 assert_operator c.trash_at, :<, db_current_time
1021 assert_operator c.delete_at, :<, db_current_time
1024 ['zzzzz-4zz18-mto52zx1s7sn3ih', # expired_collection
1025 :empty_collection_name_in_active_user_home_project,
1027 test "trash collection #{fixture} via trash action with grace period" do
1028 if fixture.is_a? String
1031 uuid = collections(fixture).uuid
1033 authorize_with :active
1034 time_before_trashing = db_current_time
1039 c = Collection.find_by_uuid(uuid)
1040 assert_operator c.trash_at, :<, db_current_time
1041 assert_operator c.delete_at, :>=, time_before_trashing + Rails.configuration.default_trash_lifetime
1045 test 'untrash a trashed collection' do
1046 authorize_with :active
1048 id: collections(:expired_collection).uuid,
1051 assert_equal false, json_response['is_trashed']
1052 assert_nil json_response['trash_at']
1055 test 'untrash error on not trashed collection' do
1056 authorize_with :active
1058 id: collections(:collection_owned_by_active).uuid,
1063 [:active, :admin].each do |user|
1064 test "get trashed collections as #{user}" do
1067 filters: [["is_trashed", "=", true]],
1068 include_trash: true,
1070 assert_response :success
1073 json_response["items"].each do |coll|
1074 items << coll['uuid']
1077 assert_includes(items, collections('unique_expired_collection')['uuid'])
1079 assert_includes(items, collections('unique_expired_collection2')['uuid'])
1081 assert_not_includes(items, collections('unique_expired_collection2')['uuid'])
1086 test 'untrash collection with same name as another with no ensure unique name' do
1087 authorize_with :active
1089 id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid,
1094 test 'untrash collection with same name as another with ensure unique name' do
1095 authorize_with :active
1097 id: collections(:trashed_collection_to_test_name_conflict_on_untrash).uuid,
1098 ensure_unique_name: true
1101 assert_equal false, json_response['is_trashed']
1102 assert_nil json_response['trash_at']
1103 assert_nil json_response['delete_at']
1104 assert_match /^same name for trashed and persisted collections \(\d{4}-\d\d-\d\d.*?Z\)$/, json_response['name']
1107 test 'cannot show collection in trashed subproject' do
1108 authorize_with :active
1110 id: collections(:collection_in_trashed_subproject).uuid,
1116 test 'can show collection in untrashed subproject' do
1117 authorize_with :active
1118 Group.find_by_uuid(groups(:trashed_project).uuid).update! is_trashed: false
1120 id: collections(:collection_in_trashed_subproject).uuid,
1123 assert_response :success
1126 test 'cannot index collection in trashed subproject' do
1127 authorize_with :active
1128 get :index, { limit: 1000 }
1129 assert_response :success
1130 item_uuids = json_response['items'].map do |item|
1133 assert_not_includes(item_uuids, collections(:collection_in_trashed_subproject).uuid)
1136 test 'can index collection in untrashed subproject' do
1137 authorize_with :active
1138 Group.find_by_uuid(groups(:trashed_project).uuid).update! is_trashed: false
1139 get :index, { limit: 1000 }
1140 assert_response :success
1141 item_uuids = json_response['items'].map do |item|
1144 assert_includes(item_uuids, collections(:collection_in_trashed_subproject).uuid)
1147 test 'can index trashed subproject collection with include_trash' do
1148 authorize_with :active
1150 include_trash: true,
1153 assert_response :success
1154 item_uuids = json_response['items'].map do |item|
1157 assert_includes(item_uuids, collections(:collection_in_trashed_subproject).uuid)
1160 test 'can get collection with past versions' do
1161 authorize_with :active
1163 filters: [['uuid','=',collections(:collection_owned_by_active).uuid]],
1164 include_old_versions: true
1166 assert_response :success
1167 assert_equal 2, assigns(:objects).length
1168 assert_equal 2, json_response['items_available']
1169 assert_equal 2, json_response['items'].count
1170 json_response['items'].each do |c|
1171 assert_equal collections(:collection_owned_by_active).uuid,
1172 c['current_version_uuid'],
1173 'response includes a version from a different collection'
1177 test 'version and current_version_uuid are ignored at creation time' do
1178 permit_unsigned_manifests
1179 authorize_with :active
1180 manifest_text = ". d41d8cd98f00b204e9800998ecf8427e 0:0:foo.txt\n"
1183 name: 'Test collection',
1185 current_version_uuid: collections(:collection_owned_by_active).uuid,
1186 manifest_text: manifest_text,
1187 # portable_data_hash: "d30fe8ae534397864cb96c544f4cf102+47"
1190 assert_response :success
1191 resp = JSON.parse(@response.body)
1192 assert_equal 1, resp['version']
1193 assert_equal resp['uuid'], resp['current_version_uuid']