-
Notifications
You must be signed in to change notification settings - Fork 21.5k
/
encrypted_attribute_type.rb
151 lines (126 loc) · 4.94 KB
/
encrypted_attribute_type.rb
1
2
3
4
5
6
7
8
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# frozen_string_literal: true
module ActiveRecord
module Encryption
# An ActiveModel::Type::Value that encrypts/decrypts strings of text.
#
# This is the central piece that connects the encryption system with +encrypts+ declarations in the
# model classes. Whenever you declare an attribute as encrypted, it configures an +EncryptedAttributeType+
# for that attribute.
class EncryptedAttributeType < ::ActiveRecord::Type::Text
include ActiveModel::Type::Helpers::Mutable
attr_reader :scheme, :cast_type
delegate :key_provider, :downcase?, :deterministic?, :previous_schemes, :with_context, :fixed?, to: :scheme
delegate :accessor, to: :cast_type
# === Options
#
# * <tt>:scheme</tt> - A +Scheme+ with the encryption properties for this attribute.
# * <tt>:cast_type</tt> - A type that will be used to serialize (before encrypting) and deserialize
# (after decrypting). ActiveModel::Type::String by default.
def initialize(scheme:, cast_type: ActiveModel::Type::String.new, previous_type: false, default: nil)
super()
@scheme = scheme
@cast_type = cast_type
@previous_type = previous_type
@default = default
end
def cast(value)
cast_type.cast(value)
end
def deserialize(value)
cast_type.deserialize decrypt(value)
end
def serialize(value)
if serialize_with_oldest?
serialize_with_oldest(value)
else
serialize_with_current(value)
end
end
def changed_in_place?(raw_old_value, new_value)
old_value = raw_old_value.nil? ? nil : deserialize(raw_old_value)
old_value != new_value
end
def previous_types # :nodoc:
@previous_types ||= {} # Memoizing on support_unencrypted_data so that we can tweak it during tests
@previous_types[support_unencrypted_data?] ||= build_previous_types_for(previous_schemes_including_clean_text)
end
def support_unencrypted_data?
ActiveRecord::Encryption.config.support_unencrypted_data && scheme.support_unencrypted_data? && !previous_type?
end
private
def previous_schemes_including_clean_text
previous_schemes.including((clean_text_scheme if support_unencrypted_data?)).compact
end
def previous_types_without_clean_text
@previous_types_without_clean_text ||= build_previous_types_for(previous_schemes)
end
def build_previous_types_for(schemes)
schemes.collect do |scheme|
EncryptedAttributeType.new(scheme: scheme, previous_type: true)
end
end
def previous_type?
@previous_type
end
def decrypt(value)
with_context do
unless value.nil?
if @default && @default == value
value
else
encryptor.decrypt(value, **decryption_options)
end
end
end
rescue ActiveRecord::Encryption::Errors::Base => error
if previous_types_without_clean_text.blank?
handle_deserialize_error(error, value)
else
try_to_deserialize_with_previous_encrypted_types(value)
end
end
def try_to_deserialize_with_previous_encrypted_types(value)
previous_types.each.with_index do |type, index|
break type.deserialize(value)
rescue ActiveRecord::Encryption::Errors::Base => error
handle_deserialize_error(error, value) if index == previous_types.length - 1
end
end
def handle_deserialize_error(error, value)
if error.is_a?(Errors::Decryption) && support_unencrypted_data?
value
else
raise error
end
end
def serialize_with_oldest?
@serialize_with_oldest ||= fixed? && previous_types_without_clean_text.present?
end
def serialize_with_oldest(value)
previous_types.first.serialize(value)
end
def serialize_with_current(value)
casted_value = cast_type.serialize(value)
casted_value = casted_value&.downcase if downcase?
encrypt(casted_value.to_s) unless casted_value.nil?
end
def encrypt(value)
with_context do
encryptor.encrypt(value, **encryption_options)
end
end
def encryptor
ActiveRecord::Encryption.encryptor
end
def encryption_options
@encryption_options ||= { key_provider: key_provider, cipher_options: { deterministic: deterministic? } }.compact
end
def decryption_options
@decryption_options ||= { key_provider: key_provider }.compact
end
def clean_text_scheme
@clean_text_scheme ||= ActiveRecord::Encryption::Scheme.new(downcase: downcase?, encryptor: ActiveRecord::Encryption::NullEncryptor.new)
end
end
end
end