Class: ActiveStorageEncryption::EncryptedGCSService
- Inherits:
-
ActiveStorage::Service::GCSService
- Object
- ActiveStorage::Service::GCSService
- ActiveStorageEncryption::EncryptedGCSService
- Includes:
- PrivateUrlPolicy
- Defined in:
- lib/active_storage_encryption/encrypted_gcs_service.rb
Direct Known Subclasses
Constant Summary collapse
- GCS_ENCRYPTION_KEY_LENGTH_BYTES =
google wants to get a 32 byte key
32
Constants included from PrivateUrlPolicy
PrivateUrlPolicy::DEFAULT_POLICY
Instance Method Summary collapse
- #compose(source_keys, destination_key, encryption_key:, filename: nil, content_type: nil, disposition: nil, custom_metadata: {}) ⇒ Object
- #download(key, encryption_key: nil, &block) ⇒ Object
- #download_chunk(key, range, encryption_key: nil) ⇒ Object
- #encrypted? ⇒ Boolean
- #headers_for_direct_upload(key, checksum:, encryption_key:, filename: nil, disposition: nil, content_type: nil, custom_metadata: {}) ⇒ Object
- #public? ⇒ Boolean
- #service_name ⇒ Object
-
#stream(key, encryption_key: nil) ⇒ Object
Reads the file for the given key in chunks, yielding each to the block.
- #upload(key, io, encryption_key: nil, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {}) ⇒ Object
- #url_for_direct_upload(key, expires_in:, checksum:, encryption_key:, content_type: nil, custom_metadata: {}, filename: nil) ⇒ Object
Methods included from PrivateUrlPolicy
#initialize, #private_url_for_streaming_via_controller, #private_url_policy, #private_url_policy=
Instance Method Details
#compose(source_keys, destination_key, encryption_key:, filename: nil, content_type: nil, disposition: nil, custom_metadata: {}) ⇒ Object
112 113 114 115 116 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 112 def compose(source_keys, destination_key, encryption_key:, filename: nil, content_type: nil, disposition: nil, custom_metadata: {}) # Because we will always have a different encryption_key on a blob when created and google requires us to have the same encryption_keys on all source blobs # we need to work this out a bit more. For now we don't need this and thus won't support it in this service. raise NotImplementedError, "Currently composing files is not supported" end |
#download(key, encryption_key: nil, &block) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 75 def download(key, encryption_key: nil, &block) if block_given? instrument :streaming_download, key: key do stream(key, encryption_key: encryption_key, &block) end else instrument :download, key: key do file_for(key).download(encryption_key: derive_service_encryption_key(encryption_key)).string rescue Google::Cloud::NotFoundError => e raise ActiveStorage::FileNotFoundError, e end end end |
#download_chunk(key, range, encryption_key: nil) ⇒ Object
89 90 91 92 93 94 95 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 89 def download_chunk(key, range, encryption_key: nil) instrument :download_chunk, key: key, range: range do file_for(key).download(range: range, encryption_key: derive_service_encryption_key(encryption_key)).string rescue Google::Cloud::NotFoundError => e raise ActiveStorage::FileNotFoundError, e end end |
#encrypted? ⇒ Boolean
10 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 10 def encrypted? = true |
#headers_for_direct_upload(key, checksum:, encryption_key:, filename: nil, disposition: nil, content_type: nil, custom_metadata: {}) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 60 def headers_for_direct_upload(key, checksum:, encryption_key:, filename: nil, disposition: nil, content_type: nil, custom_metadata: {}, **) headers = { "Content-Type" => content_type, "Content-MD5" => checksum, # Not strictly required, but it ensures the file bytes we upload match what we want. This way google will error when we upload garbage. **gcs_encryption_key_headers(derive_service_encryption_key(encryption_key)), **() } headers["Content-Disposition"] = content_disposition_with(type: disposition, filename: filename) if filename if @config[:cache_control].present? headers["Cache-Control"] = @config[:cache_control] end headers end |
#public? ⇒ Boolean
12 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 12 def public? = false |
#service_name ⇒ Object
14 15 16 17 18 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 14 def service_name # ActiveStorage::Service::DiskService => Disk # Overridden because in Rails 8 this is "self.class.name.split("::").third.remove("Service")" self.class.name.split("::").last.remove("Service") end |
#stream(key, encryption_key: nil) ⇒ Object
Reads the file for the given key in chunks, yielding each to the block.
98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 98 def stream(key, encryption_key: nil) file = file_for(key, skip_lookup: false) chunk_size = 5.megabytes offset = 0 raise ActiveStorage::FileNotFoundError unless file.present? while offset < file.size yield file.download(range: offset..(offset + chunk_size - 1), encryption_key: derive_service_encryption_key(encryption_key)).string offset += chunk_size end end |
#upload(key, io, encryption_key: nil, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {}) ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 20 def upload(key, io, encryption_key: nil, checksum: nil, content_type: nil, disposition: nil, filename: nil, custom_metadata: {}) instrument :upload, key: key, checksum: checksum do # GCS's signed URLs don't include params such as response-content-type response-content_disposition # in the signature, which means an attacker can modify them and bypass our effort to force these to # binary and attachment when the file's content type requires it. The only way to force them is to # store them as object's metadata. content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename bucket.create_file(io, key, md5: checksum, cache_control: @config[:cache_control], content_type: content_type, content_disposition: content_disposition, metadata: , encryption_key: derive_service_encryption_key(encryption_key)) rescue Google::Cloud::InvalidArgumentError => e raise ActiveStorage::IntegrityError, e end end |
#url_for_direct_upload(key, expires_in:, checksum:, encryption_key:, content_type: nil, custom_metadata: {}, filename: nil) ⇒ Object
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/active_storage_encryption/encrypted_gcs_service.rb', line 33 def url_for_direct_upload(key, expires_in:, checksum:, encryption_key:, content_type: nil, custom_metadata: {}, filename: nil, **) instrument :url, key: key do |payload| headers = headers_for_direct_upload(key, checksum:, encryption_key:, content_type:, filename:, custom_metadata:) version = :v4 args = { content_md5: checksum, expires: expires_in, headers: headers, method: "PUT", version: version } if @config[:iam] args[:issuer] = issuer args[:signer] = signer end generated_url = bucket.signed_url(key, **args) payload[:url] = generated_url generated_url end end |