active_storage_encryption 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +22 -3
- data/Rakefile +0 -2
- data/config/routes.rb +1 -1
- data/gemfiles/rails_7.gemfile.lock +9 -1
- data/gemfiles/rails_8.gemfile.lock +9 -1
- data/lib/active_storage_encryption/encrypted_blob_proxy_controller.rb +116 -0
- data/lib/active_storage_encryption/encrypted_blobs_controller.rb +0 -51
- data/lib/active_storage_encryption/encrypted_s3_service.rb +1 -0
- data/lib/active_storage_encryption/overrides.rb +1 -0
- data/lib/active_storage_encryption/private_url_policy.rb +4 -2
- data/lib/active_storage_encryption/version.rb +1 -1
- data/lib/active_storage_encryption.rb +1 -0
- data/test/dummy/log/development.log +304 -0
- data/test/dummy/log/test.log +66758 -811
- data/test/dummy/storage/0a/mt/0amtaps713liftrtbxt9h998epz4 +0 -0
- data/test/dummy/storage/0b/93/0b93pygovuunam1a3ovzwmrbuw2x +0 -0
- data/test/dummy/storage/0m/3s/0m3s7r3nboblijr1jxlnvm3p3l4b +0 -0
- data/test/dummy/storage/0o/9s/0o9s4ctbpu757qh7ucyony0itek4 +0 -0
- data/test/dummy/storage/1e/q6/1eq646og0wazgfw7bwjqz2uem0g4 +0 -0
- data/test/dummy/storage/1n/o3/1no30cpwrm727bm6arvb7zxagdg1 +0 -0
- data/test/dummy/storage/1x/6w/1x6wsoq3pew17reztwax78lrr3hc +0 -0
- data/test/dummy/storage/28/de/28deswrv89c9f2tk7dz1l5uovd4r +0 -0
- data/test/dummy/storage/2h/sd/2hsd1mh20c6os2nzyoicfyymhwev +0 -0
- data/test/dummy/storage/2t/ni/2tnidhdk4c6cj0tnw3jiw88dgs4g +0 -0
- data/test/dummy/storage/2v/e0/2ve0555nluisy2el5cf4txzgae3j +0 -0
- data/test/dummy/storage/2z/c5/2zc5mj8g0o9l7mfnim0vs4v48xd6 +0 -0
- data/test/dummy/storage/34/xc/34xc9hk74dm9227d6mhgfcfxl4ue +0 -0
- data/test/dummy/storage/3z/0t/3z0tnve7ivrq0qyrvfhfzztjhjqs +0 -0
- data/test/dummy/storage/49/14/4914188q1dptpw4po91cp54f32bg +0 -0
- data/test/dummy/storage/4c/74/4c7412lfz0pm2ocg6u01h67bnsch +0 -0
- data/test/dummy/storage/52/qf/52qfbgjlf3gor3agsyrt09t19o55 +0 -0
- data/test/dummy/storage/57/go/57gok1uc4ebc3ugrjrje4lpe1ram +0 -0
- data/test/dummy/storage/5f/dv/5fdvt6tu1mkyajbz4hbxbw6fpt9w +0 -0
- data/test/dummy/storage/5x/b7/5xb7zzi66fi5f6yrn09pq4ogb9wo +0 -0
- data/test/dummy/storage/6m/vr/6mvr1fr5it125tm4vahjw6bv9wkz +0 -0
- data/test/dummy/storage/7b/hb/7bhbdxqn67lape1f49jqfktcei4n +0 -0
- data/test/dummy/storage/7n/4v/7n4vpm1q14y4qffc4jj78m036gtw +0 -0
- data/test/dummy/storage/7q/ku/7qkufbjwbbqwnf89uciosleixnew +0 -0
- data/test/dummy/storage/8l/5v/8l5vb4o02hx46s5qohfn5to945p3 +0 -0
- data/test/dummy/storage/8q/pu/8qpun3f8vzl7auxajvqyq8f48ngw +0 -0
- data/test/dummy/storage/8w/ag/8wag4ptmox207h7mobamk0tcebwx +0 -0
- data/test/dummy/storage/8w/v8/8wv8lrhsw4s2r6guh1csd3jd89ii +0 -0
- data/test/dummy/storage/9b/c6/9bc6wlpfnqdywpnxgeoin3w9b5ch +0 -0
- data/test/dummy/storage/9l/wk/9lwkt21k5iburdaitbwliw7krtwt +0 -0
- data/test/dummy/storage/9p/0v/9p0vgfw3l2854k7so3rp33rmyh7p +0 -0
- data/test/dummy/storage/9r/sy/9rsya3r6syft34qz24g1h4u4qq44 +0 -0
- data/test/dummy/storage/9s/es/9seslusr46xjf3mfzq10hkp13kc1 +0 -0
- data/test/dummy/storage/9t/nv/9tnvn5v52fkvurpgszf4gco78t5h +0 -0
- data/test/dummy/storage/9u/to/9utokgxyu6xyovandu7pjhogoaqp +0 -0
- data/test/dummy/storage/9w/a4/9wa4c20p4dvm1cd5thnv9f7ei13w +0 -0
- data/test/dummy/storage/at/kg/atkgs5gwz2xdv9lvqftsg6p7gcpu +0 -0
- data/test/dummy/storage/at/qo/atqomgf3rpb2f6e1tq1yn2xqzojv +0 -0
- data/test/dummy/storage/ba/lq/balqtije6kf82ht4lr70ajaae9kc +0 -0
- data/test/dummy/storage/bf/i1/bfi1ij9rygr6lkx1r0lhgi8o5smx +0 -0
- data/test/dummy/storage/bg/ye/bgyenotrv3aj6lk88edwv0c41pfj +0 -0
- data/test/dummy/storage/bu/xe/buxed4b1l78kcax53fa37awm9ywk +0 -0
- data/test/dummy/storage/d2/c1/d2c11nhikb474oq3q7so0xbhukvj +0 -0
- data/test/dummy/storage/development.sqlite3 +0 -0
- data/test/dummy/storage/dk/hy/dkhybxn2o27a8xgvfhsxpgqxa1zf +0 -0
- data/test/dummy/storage/e7/2n/e72nz5cz3wf6qvh4dw4qfnw6ucog +0 -0
- data/test/dummy/storage/eo/4q/eo4qn68m7al0ehhe0s23ycuzkjto +0 -0
- data/test/dummy/storage/ew/8s/ew8sejdsx8ddmrzkvfa37ebz1ts1 +0 -0
- data/test/dummy/storage/f8/q1/f8q1kpg2tou8ru0afj8d2gy6ym5p +0 -0
- data/test/dummy/storage/fr/55/fr558uhp1k93jzhb4butyi2ry51t +0 -0
- data/test/dummy/storage/g4/nh/g4nhx1zxbeiegqpgn8ppsl1yhm0t +0 -0
- data/test/dummy/storage/gg/r5/ggr51egxhqfh4w5eluzs47qceb76 +0 -0
- data/test/dummy/storage/gh/ua/ghuaagralqmjy8rkbwmuv3010lvs +0 -0
- data/test/dummy/storage/gx/uh/gxuhmf52ufli3m7ng8irp8ghxa1v +0 -0
- data/test/dummy/storage/h0/m1/h0m1emy251xus1d9qh6u25dzy18o +0 -0
- data/test/dummy/storage/hh/kc/hhkc2q8paptyvhw2m5hlwylhtfo5 +0 -0
- data/test/dummy/storage/hq/0q/hq0q04kr6qzrp0qaee8rehcp2tzx +0 -0
- data/test/dummy/storage/ii/g1/iig1ge3fsjitai4g2fkq4qt369wh +0 -0
- data/test/dummy/storage/io/f0/iof0mv7w8qjd6m826g52pzyxedet +0 -0
- data/test/dummy/storage/jk/2i/jk2ifmx6ac35ubk3esufnm6bn1m1 +0 -0
- data/test/dummy/storage/jw/4t/jw4trdeyfkw3j8z70xcnr9a7gqe5 +0 -0
- data/test/dummy/storage/ke/k2/kek24leksglm1rs2a78mfmot0p3s +0 -0
- data/test/dummy/storage/kh/6d/kh6doaxxwxiyes0yqz2dmmpajkzv +0 -0
- data/test/dummy/storage/kj/7n/kj7nookjhisagd80z8hlv3wn50am +0 -0
- data/test/dummy/storage/kq/lf/kqlf5udtrgrk4v55qodxyt6i68p8 +0 -0
- data/test/dummy/storage/ky/33/ky334jbo8eb08pj9qbe919iz91mh +0 -0
- data/test/dummy/storage/lt/zw/ltzw4lur2bheit1273ogpfzhv7j1 +0 -0
- data/test/dummy/storage/m2/ve/m2vejmyttn1ium81dopppom6vum6 +0 -0
- data/test/dummy/storage/m8/d4/m8d4r9iauedq8wlpvnx1f3ou0jwg +0 -0
- data/test/dummy/storage/m9/ee/m9eetioklzatyff94gq0vn1cga1n +0 -0
- data/test/dummy/storage/ma/v0/mav084zvmyoh1a8i7dcwqy2aaoi9 +0 -0
- data/test/dummy/storage/mg/pa/mgpauiu02i28j3poef65k3q0gfpw +0 -0
- data/test/dummy/storage/mm/8g/mm8gp5evncb1ol1lj2jlmra2ixij +0 -0
- data/test/dummy/storage/mm/d2/mmd21x8c1amgnidzw0wowiwug4g3 +0 -0
- data/test/dummy/storage/n2/qr/n2qro0y9heko9cwxlf10wiqiipsw +0 -0
- data/test/dummy/storage/n8/b7/n8b7b7qgu6jtw577dnn10jrrmszs +0 -0
- data/test/dummy/storage/n8/p2/n8p2ine0qqhphn09kqtxco4y7g0a +0 -0
- data/test/dummy/storage/nk/vh/nkvhgk7snpdy2ak6k02htxx9swp7 +0 -0
- data/test/dummy/storage/nn/s0/nns0nggo0x645ytco52adnsi4myp +0 -0
- data/test/dummy/storage/nu/kz/nukzl7cckkzh68i7kyjkm9mzw7c0 +0 -0
- data/test/dummy/storage/nv/8v/nv8vyoghcde1yr1bjpsw4327qt7s +0 -0
- data/test/dummy/storage/of/on/ofonhf1gs26k3dpj6o7b0ktzfowh +0 -0
- data/test/dummy/storage/pl/pf/plpfs59hvdoogj9gdweqta36csqn +0 -0
- data/test/dummy/storage/q5/g5/q5g55ekmscu10pzfw6q4syigt81g +0 -0
- data/test/dummy/storage/q5/kc/q5kcr9twyb9v4mh31pay0t7nkuwu +0 -0
- data/test/dummy/storage/qa/xd/qaxdngi74r52ahqg1pz8hjddeajc +0 -0
- data/test/dummy/storage/r7/5v/r75vadn34ak53vinylgnfdl1s8rt +0 -0
- data/test/dummy/storage/rj/rg/rjrghnyzyvxpkjw1a57mrloz72x1 +0 -0
- data/test/dummy/storage/se/h7/seh7eorfoanpp6de62pubv7kyu1a +0 -0
- data/test/dummy/storage/sj/i1/sji1oj12soz2fcjcoz0gejvzo8to +0 -0
- data/test/dummy/storage/sn/2r/sn2rku9thay4hbcbt926an69maku +0 -0
- data/test/dummy/storage/sw/jm/swjmbmxou3tnarcirxc6gdycxh91 +0 -0
- data/test/dummy/storage/sz/mq/szmqlydvpgqaw7p3v0wh444wtcif +0 -0
- data/test/dummy/storage/test.sqlite3 +0 -0
- data/test/dummy/storage/tg/by/tgbyrdvg94ivhhy2z59e8l9fod10 +0 -0
- data/test/dummy/storage/u5/vm/u5vmz08tuayqggd436et8fiaeml1 +0 -0
- data/test/dummy/storage/u6/pf/u6pf4yky0vbmvid3fa3lm4lre68g +0 -0
- data/test/dummy/storage/ub/ql/ubqlmlilt8ujgdpngcm1zae41kgy +0 -0
- data/test/dummy/storage/un/29/un29e9khqism72ag27ojccmn5sds +0 -0
- data/test/dummy/storage/ux/ns/uxnsvuk4rr1p67n1oq6tmraz0gaw +0 -0
- data/test/dummy/storage/v1/qo/v1qor0zxg3lctk9mbwyos3oag9gj +0 -0
- data/test/dummy/storage/v8/ok/v8okmd7374w1obna13a7anllx2vu +0 -0
- data/test/dummy/storage/vd/tf/vdtfmz2ctis3dr1r35do9bow2xj5 +0 -0
- data/test/dummy/storage/vo/dg/vodgq1inccnujjt3auber7tt8w8o +0 -0
- data/test/dummy/storage/vp/oe/vpoeiq00tf9pk0jcjlccomkju1zc +0 -0
- data/test/dummy/storage/vu/kg/vukgoj6qf96bhealui2yaeyn4n72 +0 -0
- data/test/dummy/storage/w7/2z/w72zoqu7v6v6jp0tpy671dcbvpow +0 -0
- data/test/dummy/storage/wa/3f/wa3fncsnozc6n4xfu32gw34geqcd +0 -0
- data/test/dummy/storage/wy/ix/wyixbqx3f6a4agb8bjhrtpblpaua +0 -0
- data/test/dummy/storage/xd/st/xdsttma3tqt7mex0vhp1vsm3dq16 +0 -0
- data/test/dummy/storage/xv/ej/xvejm2e064bnpunx3nmktaqs0x90 +0 -0
- data/test/dummy/storage/xx/py/xxpyyodssq2xmp57qrtvuw0wchwk +0 -0
- data/test/dummy/storage/xz/ik/xzikejc5sohi3zexa93s9xmg4jst +0 -0
- data/test/dummy/storage/y4/g8/y4g8teo86blcv0zysa2d2jawvk6i +0 -0
- data/test/dummy/storage/y9/58/y958xli6aoktx1ehuyjc1k8dcbzv +0 -0
- data/test/dummy/storage/yj/lw/yjlw8bf70iujb16deja8ae43rqbc +0 -0
- data/test/dummy/storage/z3/qy/z3qyb9avbucwhxa8909rpfued0y5 +0 -0
- data/test/dummy/storage/zr/wu/zrwudcg4kgo7r0jemszuzok8grqp +0 -0
- data/test/dummy/tmp/local_secret.txt +1 -0
- data/test/integration/encrypted_blob_proxy_controller_test.rb +253 -0
- data/test/integration/encrypted_blobs_controller_test.rb +0 -130
- data/test/lib/encrypted_disk_service_test.rb +5 -119
- data/test/lib/encrypted_mirror_service_test.rb +1 -1
- data/test/lib/encrypted_s3_service_test.rb +5 -2
- metadata +137 -4
- data/test/dummy/storage/x6/pl/x6plznfuhrsyjn9pox2a6xgmcs3x +0 -0
- data/test/dummy/storage/yq/sv/yqsvw5a72b3fv719zq8a6yb7lv0j +0 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
618685350e7e01cbbeeb70c27ec07e8b83919c4fa7fac6831a0f1f8a13d2d2a03e2088e434515ddf25dfe4c62c21efd22410011d56678caf826e27d7b365d24a
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class ActiveStorageEncryptionEncryptedBlobProxyControllerTest < ActionDispatch::IntegrationTest
|
|
6
|
+
setup do
|
|
7
|
+
@storage_dir = Dir.mktmpdir
|
|
8
|
+
@other_storage_dir = Dir.mktmpdir
|
|
9
|
+
@service = ActiveStorageEncryption::EncryptedDiskService.new(root: @storage_dir, private_url_policy: "stream")
|
|
10
|
+
@service.name = "amazing_encrypting_disk_service" # Needed for the controller and service lookup
|
|
11
|
+
|
|
12
|
+
# Hack: sneakily add our service to them configurations
|
|
13
|
+
# ActiveStorage::Blob.services.send(:services)["amazing_encrypting_disk_service"] = @service
|
|
14
|
+
|
|
15
|
+
# We need to set our service as the default, because the controller does lookup from the application config -
|
|
16
|
+
# which does not include the service we define here
|
|
17
|
+
@previous_default_service = ActiveStorage::Blob.service
|
|
18
|
+
@previous_services = ActiveStorage::Blob.services
|
|
19
|
+
|
|
20
|
+
# To catch potential issues where something goes to the default service by mistake, let's set a
|
|
21
|
+
# different Service as the default
|
|
22
|
+
@non_encrypted_default_service = ActiveStorage::Service::DiskService.new(root: @other_storage_dir)
|
|
23
|
+
ActiveStorage::Blob.service = @non_encrypted_default_service
|
|
24
|
+
ActiveStorage::Blob.services = {@service.name => @service} # That too
|
|
25
|
+
|
|
26
|
+
# This needs to be set
|
|
27
|
+
ActiveStorageEncryption::Engine.routes.default_url_options = {host: "www.example.com"}
|
|
28
|
+
|
|
29
|
+
# We need to use a hostname for ActiveStorage which is in the Rails authorized hosts.
|
|
30
|
+
# see https://stackoverflow.com/a/60573259/153886
|
|
31
|
+
ActiveStorage::Current.url_options = {
|
|
32
|
+
host: "www.example.com",
|
|
33
|
+
protocol: "https"
|
|
34
|
+
}
|
|
35
|
+
freeze_time # For testing expiring tokens
|
|
36
|
+
https! # So that all requests are simulated as SSL
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def teardown
|
|
40
|
+
unfreeze_time
|
|
41
|
+
ActiveStorage::Blob.service = @previous_default_service
|
|
42
|
+
ActiveStorage::Blob.services = @previous_services
|
|
43
|
+
FileUtils.rm_rf(@storage_dir)
|
|
44
|
+
FileUtils.rm_rf(@other_storage_dir)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def engine_routes
|
|
48
|
+
ActiveStorageEncryption::Engine.routes.url_helpers
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
test "show() serves the complete decrypted blob body" do
|
|
52
|
+
rng = Random.new(Minitest.seed)
|
|
53
|
+
plaintext = rng.bytes(512)
|
|
54
|
+
|
|
55
|
+
blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(plaintext), content_type: "x-office/severance", filename: "secret.bin", service_name: @service.name)
|
|
56
|
+
assert blob.encryption_key
|
|
57
|
+
|
|
58
|
+
streaming_url = blob.url(disposition: "inline") # This generates a URL with the byte size
|
|
59
|
+
get streaming_url
|
|
60
|
+
|
|
61
|
+
assert_response :success
|
|
62
|
+
assert_equal "x-office/severance", response.headers["content-type"]
|
|
63
|
+
assert_equal blob.key.inspect, response.headers["etag"]
|
|
64
|
+
assert_equal plaintext, response.body
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
test "show() serves a blob of 0 size" do
|
|
68
|
+
Random.new(Minitest.seed)
|
|
69
|
+
plaintext = "".b
|
|
70
|
+
|
|
71
|
+
blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(plaintext), content_type: "x-office/severance", filename: "secret.bin", service_name: @service.name)
|
|
72
|
+
assert blob.encryption_key
|
|
73
|
+
|
|
74
|
+
streaming_url = blob.url(disposition: "inline") # This generates a URL with the byte size
|
|
75
|
+
get streaming_url
|
|
76
|
+
|
|
77
|
+
assert_response :success
|
|
78
|
+
assert response.body.empty?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
test "show() returns a 404 when the blob no longer exists on the service" do
|
|
82
|
+
Random.new(Minitest.seed)
|
|
83
|
+
plaintext = "hello"
|
|
84
|
+
|
|
85
|
+
blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(plaintext), content_type: "x-office/severance", filename: "secret.bin", service_name: @service.name)
|
|
86
|
+
assert blob.encryption_key
|
|
87
|
+
|
|
88
|
+
streaming_url = blob.url(disposition: "inline") # This generates a URL with the byte size
|
|
89
|
+
blob.service.delete(blob.key)
|
|
90
|
+
|
|
91
|
+
get streaming_url
|
|
92
|
+
|
|
93
|
+
assert_response :not_found
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
test "show() serves HTTP ranges" do
|
|
97
|
+
rng = Random.new(Minitest.seed)
|
|
98
|
+
plaintext = rng.bytes(5.megabytes + 13)
|
|
99
|
+
|
|
100
|
+
blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(plaintext), content_type: "x-office/severance", filename: "secret.bin", service_name: @service.name)
|
|
101
|
+
assert blob.encryption_key
|
|
102
|
+
|
|
103
|
+
streaming_url = blob.url(disposition: "inline") # This generates a URL with the byte size
|
|
104
|
+
get streaming_url, headers: {"Range" => "bytes=0-0"}
|
|
105
|
+
|
|
106
|
+
assert_response :partial_content
|
|
107
|
+
assert_equal "1", response.headers["content-length"]
|
|
108
|
+
assert_equal "bytes 0-0/5242893", response.headers["content-range"]
|
|
109
|
+
assert_equal "x-office/severance", response.headers["content-type"]
|
|
110
|
+
assert_equal plaintext[0..0], response.body
|
|
111
|
+
|
|
112
|
+
get streaming_url, headers: {"Range" => "bytes=1-2"}
|
|
113
|
+
|
|
114
|
+
assert_response :partial_content
|
|
115
|
+
assert_equal "2", response.headers["content-length"]
|
|
116
|
+
assert_equal "bytes 1-2/5242893", response.headers["content-range"]
|
|
117
|
+
assert_equal "x-office/severance", response.headers["content-type"]
|
|
118
|
+
assert_equal plaintext[1..2], response.body
|
|
119
|
+
|
|
120
|
+
get streaming_url, headers: {"Range" => "bytes=1-2,8-10,12-23"}
|
|
121
|
+
|
|
122
|
+
assert_response :partial_content
|
|
123
|
+
assert response.headers["content-type"].start_with?("multipart/byteranges; boundary=")
|
|
124
|
+
assert_nil response.headers["content-range"]
|
|
125
|
+
assert_equal 350, response.body.bytesize
|
|
126
|
+
|
|
127
|
+
get streaming_url, headers: {"Range" => "bytes=99999999999999999-99999999999999999"}
|
|
128
|
+
assert_response :range_not_satisfiable
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
test "show() refuses a request which goes to a non-encrypted Service" do
|
|
132
|
+
rng = Random.new(Minitest.seed)
|
|
133
|
+
|
|
134
|
+
key = SecureRandom.base36(12)
|
|
135
|
+
encryption_key = rng.bytes(32)
|
|
136
|
+
plaintext = rng.bytes(512)
|
|
137
|
+
@service.upload(key, StringIO.new(plaintext).binmode, encryption_key: encryption_key)
|
|
138
|
+
|
|
139
|
+
streaming_url = @service.url(key, encryption_key: encryption_key, filename: ActiveStorage::Filename.new("private.doc"),
|
|
140
|
+
expires_in: 30.seconds, disposition: "inline", content_type: "x-office/severance",
|
|
141
|
+
blob_byte_size: plaintext.bytesize)
|
|
142
|
+
|
|
143
|
+
# Sneak in a non-encrypted service under the same key
|
|
144
|
+
ActiveStorage::Blob.services[@service.name] = @non_encrypted_default_service
|
|
145
|
+
|
|
146
|
+
get streaming_url
|
|
147
|
+
assert_response :forbidden
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
test "show() refuses a request which has an incorrect encryption key" do
|
|
151
|
+
rng = Random.new(Minitest.seed)
|
|
152
|
+
|
|
153
|
+
key = SecureRandom.base36(12)
|
|
154
|
+
encryption_key = rng.bytes(32)
|
|
155
|
+
plaintext = rng.bytes(512)
|
|
156
|
+
@service.upload(key, StringIO.new(plaintext).binmode, encryption_key: encryption_key)
|
|
157
|
+
|
|
158
|
+
another_encryption_key = rng.bytes(32)
|
|
159
|
+
refute_equal encryption_key, another_encryption_key
|
|
160
|
+
|
|
161
|
+
streaming_url = @service.url(key, encryption_key: another_encryption_key,
|
|
162
|
+
filename: ActiveStorage::Filename.new("private.doc"), expires_in: 30.seconds,
|
|
163
|
+
disposition: "inline", content_type: "x-office/severance", blob_byte_size: plaintext.bytesize)
|
|
164
|
+
get streaming_url
|
|
165
|
+
|
|
166
|
+
assert_response :forbidden
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
test "show() refuses a request with a garbage token" do
|
|
170
|
+
get engine_routes.encrypted_blob_streaming_get_path(token: "garbage", filename: "exfil.bin")
|
|
171
|
+
assert_response :forbidden
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
test "show() refuses a request with a token that has been encrypted using an incorrect encryption key" do
|
|
175
|
+
https!
|
|
176
|
+
rng = Random.new(Minitest.seed)
|
|
177
|
+
encryptor_key = rng.bytes(32)
|
|
178
|
+
other_encryptor = ActiveStorageEncryption::TokenEncryptor.new(encryptor_key, url_safe: encryptor_key)
|
|
179
|
+
|
|
180
|
+
key = SecureRandom.base36(12)
|
|
181
|
+
encryption_key = rng.bytes(32)
|
|
182
|
+
@service.upload(key, StringIO.new(rng.bytes(512)).binmode, encryption_key: encryption_key)
|
|
183
|
+
|
|
184
|
+
streaming_url = ActiveStorageEncryption.stub(:token_encryptor, -> { other_encryptor }) do
|
|
185
|
+
@service.url(key, encryption_key: encryption_key,
|
|
186
|
+
filename: ActiveStorage::Filename.new("private.doc"), expires_in: 3.seconds,
|
|
187
|
+
disposition: "inline", content_type: "binary/octet-stream",
|
|
188
|
+
blob_byte_size: 512)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
get streaming_url
|
|
192
|
+
assert_response :forbidden
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
test "show() refuses a request with a token that has expired" do
|
|
196
|
+
rng = Random.new(Minitest.seed)
|
|
197
|
+
|
|
198
|
+
key = SecureRandom.base36(12)
|
|
199
|
+
encryption_key = rng.bytes(32)
|
|
200
|
+
@service.upload(key, StringIO.new(rng.bytes(512)).binmode, encryption_key: encryption_key)
|
|
201
|
+
|
|
202
|
+
streaming_url = @service.url(key, encryption_key: encryption_key,
|
|
203
|
+
filename: ActiveStorage::Filename.new("private.doc"), expires_in: 3.seconds,
|
|
204
|
+
disposition: "inline", content_type: "binary/octet-stream",
|
|
205
|
+
blob_byte_size: 512)
|
|
206
|
+
travel 5.seconds
|
|
207
|
+
|
|
208
|
+
get streaming_url
|
|
209
|
+
assert_response :forbidden
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
test "show() requires headers if the private_url_policy of the service is set to :require_headers" do
|
|
213
|
+
rng = Random.new(Minitest.seed)
|
|
214
|
+
|
|
215
|
+
key = SecureRandom.base36(12)
|
|
216
|
+
encryption_key = rng.bytes(32)
|
|
217
|
+
plaintext = rng.bytes(512)
|
|
218
|
+
@service.upload(key, StringIO.new(plaintext).binmode, encryption_key: encryption_key)
|
|
219
|
+
|
|
220
|
+
# The policy needs to be set before we generate the token (the token includes require_headers)
|
|
221
|
+
@service.private_url_policy = :require_headers
|
|
222
|
+
streaming_url = @service.url(key, encryption_key: encryption_key,
|
|
223
|
+
filename: ActiveStorage::Filename.new("private.doc"), expires_in: 30.seconds, disposition: "inline",
|
|
224
|
+
content_type: "x-office/severance", blob_byte_size: plaintext.bytesize)
|
|
225
|
+
|
|
226
|
+
get streaming_url
|
|
227
|
+
assert_response :forbidden # Without headers
|
|
228
|
+
|
|
229
|
+
get streaming_url, headers: {"HTTP_X_ACTIVE_STORAGE_ENCRYPTION_KEY" => Base64.strict_encode64(encryption_key)}
|
|
230
|
+
assert_response :success
|
|
231
|
+
assert_equal "x-office/severance", response.headers["content-type"]
|
|
232
|
+
assert_equal plaintext, response.body
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
test "show() refuses a request if the service no longer permits private URLs, even if the URL was generated when it used to permit them" do
|
|
236
|
+
rng = Random.new(Minitest.seed)
|
|
237
|
+
|
|
238
|
+
SecureRandom.base36(12)
|
|
239
|
+
plaintext = rng.bytes(512)
|
|
240
|
+
|
|
241
|
+
blob = ActiveStorage::Blob.create_and_upload!(io: StringIO.new(plaintext), content_type: "x-office/severance", filename: "secret.bin", service_name: @service.name)
|
|
242
|
+
assert blob.encryption_key
|
|
243
|
+
streaming_url = blob.url(disposition: "inline", content_type: "x-office/severance")
|
|
244
|
+
|
|
245
|
+
@service.private_url_policy = :disable
|
|
246
|
+
|
|
247
|
+
get streaming_url
|
|
248
|
+
assert_response :forbidden # Without headers
|
|
249
|
+
|
|
250
|
+
get streaming_url, headers: {"HTTP_X_ACTIVE_STORAGE_ENCRYPTION_KEY" => Base64.strict_encode64(blob.encryption_key)}
|
|
251
|
+
assert_response :forbidden # With headers
|
|
252
|
+
end
|
|
253
|
+
end
|