Module: ActiveStorageEncryption::Overrides::EncryptedBlobClassMethods

Defined in:
lib/active_storage_encryption/overrides.rb

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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
59
60
61
62
63
64
65
66
# File 'lib/active_storage_encryption/overrides.rb', line 9

def self.included base
  base.class_eval do
    encrypts :encryption_key
    validates :encryption_key, presence: {message: "must be present for this service"}, if: :service_encrypted?

    class << self
      ENCRYPTION_KEY_LENGTH_BYTES = 16 + 32 # So we have enough

      def service_encrypted?(service_name)
        return false unless service_name

        service = ActiveStorage::Blob.services.fetch(service_name) do
          ActiveStorage::Blob.service
        end

        !!service&.try(:encrypted?)
      end

      def generate_random_encryption_key
        SecureRandom.bytes(ENCRYPTION_KEY_LENGTH_BYTES)
      end

      def create_before_direct_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil, service_name: nil, record: nil, key: nil, encryption_key: nil)
        encryption_key = service_encrypted?(service_name) ? (encryption_key || generate_random_encryption_key) : nil
        create!(key: key, filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: , service_name: service_name, encryption_key: encryption_key)
      end

      def create_and_upload!(io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil, key: nil, encryption_key: nil)
        create_after_unfurling!(key: key, io: io, filename: filename, content_type: content_type, metadata: , service_name: service_name, identify: identify, encryption_key:).tap do |blob|
          blob.upload_without_unfurling(io)
        end
      end

      def build_after_unfurling(io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil, key: nil, encryption_key: nil)
        new(key: key, filename: filename, content_type: content_type, metadata: , service_name: service_name, encryption_key:).tap do |blob|
          blob.unfurl(io, identify: identify)
          blob.encryption_key ||= service_encrypted?(service_name) ? (encryption_key || generate_random_encryption_key) : nil
        end
      end

      def create_after_unfurling!(io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil, key: nil, encryption_key: nil)
        build_after_unfurling(key: key, io: io, filename: filename, content_type: content_type, metadata: , service_name: service_name, identify: identify, encryption_key:).tap(&:save!)
      end

      # Concatenate multiple blobs into a single "composed" blob.
      def compose(blobs, filename:, content_type: nil, metadata: nil, key: nil, service_name: nil, encryption_key: nil)
        raise ActiveRecord::RecordNotSaved, "All blobs must be persisted." if blobs.any?(&:new_record?)

        content_type ||= blobs.pluck(:content_type).compact.first

        new(key: key, filename: filename, content_type: content_type, metadata: , byte_size: blobs.sum(&:byte_size), service_name:, encryption_key:).tap do |combined_blob|
          combined_blob.compose(blobs.pluck(:key), source_encryption_keys: blobs.pluck(:encryption_key))
          combined_blob.save!
        end
      end
    end
  end
end