active_enquo 0.3.0 → 0.5.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 +74 -54
- data/active_enquo.gemspec +1 -1
- data/docs/MIGRATION.md +107 -0
- data/e2e_tests/.gitignore +1 -0
- data/e2e_tests/001_direct_migration/exercise_model +27 -0
- data/e2e_tests/001_direct_migration/migrations/001_create_people_table.rb +11 -0
- data/e2e_tests/001_direct_migration/migrations/002_encrypt_people_data.rb +25 -0
- data/e2e_tests/001_direct_migration/run +18 -0
- data/e2e_tests/helper.sh +34 -0
- data/e2e_tests/init.rb +18 -0
- data/e2e_tests/people.json +1 -0
- data/e2e_tests/run +19 -0
- data/lib/active_enquo.rb +261 -21
- metadata +14 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9d538e223b9e7485c4b08cf8ac38f2cc6a08f5d3c3b72426167606fdc7a5fdf5
|
4
|
+
data.tar.gz: 33e80addf4beb8bbe8f5328cbfd093cb70264ec2804d148026cbdabbd36cd1b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af6a5e20a215234ce3bce1656a7e02e94f536bf1a3c8d674732d7d49cd8ab5265fe4c306ea730cfe60bba707df5436cf9048d9ce8b85f021fc92639835a0424d
|
7
|
+
data.tar.gz: e9f6b9d0f5f5f5fb5b6fdecd8ba7fdcadf5b7be0c363787d78a2a989bb753b506d9d914122041614aad4cb3032803d6f5075ec02019a81457104471decd4fec7
|
data/README.md
CHANGED
@@ -3,12 +3,12 @@ This allows you to keep the data you store safe, by encrypting it, without compr
|
|
3
3
|
|
4
4
|
Sounds like magic?
|
5
5
|
Well, maybe a little bit.
|
6
|
-
Read our [how it works](https://enquo.org/how-it-works) if you're interested in the details, or read on for how to use it.
|
6
|
+
Read our [how it works](https://enquo.org/how-it-works) if you're interested in the gory cryptographic details, or read on for how to use it.
|
7
7
|
|
8
8
|
|
9
9
|
# Pre-requisites
|
10
10
|
|
11
|
-
In order to make use of
|
11
|
+
In order to make use of ActiveRecord extension, you must be running Postgres 11 or higher, with the [`pg_enquo`](https://github.com/enquo/pg_enquo) extension enabled in the database you're working in.
|
12
12
|
See [the `pg_enquo` installation guide](https://github.com/enquo/pg_enquo/tree/main/doc/installation.md) for instructions on how to install `pg_enquo`.
|
13
13
|
|
14
14
|
Also, if you're installing this gem from source, you'll need a reasonably recent [Rust](https://rust-lang.org) toolchain installed.
|
@@ -16,33 +16,41 @@ Also, if you're installing this gem from source, you'll need a reasonably recent
|
|
16
16
|
|
17
17
|
# Installation
|
18
18
|
|
19
|
-
It's a gem:
|
19
|
+
It's a gem, so the usual methods should work Just Fine:
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
```sh
|
22
|
+
gem install active_enquo
|
23
|
+
# OR
|
24
|
+
echo "gem 'active_enquo'" >> Gemfile
|
25
|
+
```
|
24
26
|
|
25
|
-
|
27
|
+
On macOS, and Linux `x86-64`/`aarch64`, you'll get a pre-built binary gem that contains everything you need.
|
28
|
+
For other platforms, you'll need to have Rust 1.59.0 or later installed in order to build the native code portion of the gem.
|
26
29
|
|
27
|
-
If you're the sturdy type that likes to run from git:
|
28
30
|
|
29
|
-
|
31
|
+
# Configuration
|
30
32
|
|
31
|
-
|
32
|
-
presumably know what to do already.
|
33
|
+
The only setting that ActiveEnquo needs is to be given a "root" key, which is used to derive the keys which are used to actually encrypt data.
|
33
34
|
|
34
35
|
|
35
|
-
|
36
|
+
## Step 1: Generate a Root Key
|
36
37
|
|
37
|
-
The
|
38
|
-
This key ***MUST*** be generated by a cryptographically-secure random number generator, and must also be 64 hex digits in length.
|
38
|
+
The ActiveEnquo root key ***MUST*** be generated by a cryptographically-secure random number generator, and must also be 64 hex digits in length.
|
39
39
|
A good way to generate this key is with the `SecureRandom` module:
|
40
40
|
|
41
41
|
```sh
|
42
42
|
ruby -r securerandom -e 'puts SecureRandom.hex(32)'
|
43
43
|
```
|
44
44
|
|
45
|
-
|
45
|
+
|
46
|
+
## Step 2: Configure Your Application
|
47
|
+
|
48
|
+
With this key in hand, you need to store it somewhere.
|
49
|
+
|
50
|
+
|
51
|
+
### Using Rails Credential Store (Recommended)
|
52
|
+
|
53
|
+
The recommended way to store your root key, at present, is in the [Rails credentials store](https://guides.rubyonrails.org/security.html#custom-credentials).
|
46
54
|
|
47
55
|
1. Open up the Rails credentials editor:
|
48
56
|
|
@@ -59,7 +67,11 @@ With this key in hand, you can store it in the Rails credential store, like this
|
|
59
67
|
|
60
68
|
3. Save and exit the editor. Commit the changes to your revision control system.
|
61
69
|
|
62
|
-
|
70
|
+
|
71
|
+
### Direct Assignment (Only If You Must)
|
72
|
+
|
73
|
+
Using the Rails credential store only works if you are using Rails, of course.
|
74
|
+
If you're using ActiveRecord by itself, you must set the root key yourself during application initialization.
|
63
75
|
You do this by assigning a `RootKey` to `ActiveEnquo.root_key`, like this:
|
64
76
|
|
65
77
|
```ruby
|
@@ -67,9 +79,12 @@ You do this by assigning a `RootKey` to `ActiveEnquo.root_key`, like this:
|
|
67
79
|
ActiveEnquo.root_key = ActiveEnquo::RootKey::Static.new("0000000000000000000000000000000000000000000000000000000000000000")
|
68
80
|
```
|
69
81
|
|
70
|
-
|
71
|
-
|
72
|
-
|
82
|
+
Preferably, you would pass the key into your application via, say, an environment variable, and then immediately clear the environment variable:
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
ActiveEnquo.root_key = ActiveEnquo::RootKey::Static.new(ENV.fetch("ENQUO_ROOT_KEY"))
|
86
|
+
ENV.delete("ENQUO_ROOT_KEY")
|
87
|
+
```
|
73
88
|
|
74
89
|
Support for cloud keystores, such as AWS KMS, GCP KMS, Azure KeyVault, and HashiCorp Vault, will be implemented sooner or later.
|
75
90
|
If you have a burning desire to see that more on the "sooner" end than "later", PRs are welcome.
|
@@ -77,67 +92,78 @@ If you have a burning desire to see that more on the "sooner" end than "later",
|
|
77
92
|
|
78
93
|
# Usage
|
79
94
|
|
80
|
-
|
95
|
+
We try to make using ActiveEnquo as simple as possible.
|
96
|
+
|
97
|
+
|
98
|
+
## Create Your Encrypted Column
|
99
|
+
|
100
|
+
Start by creating a column in your database that uses one of the [available `enquo_*` types](https://github.com/enquo/pg_enquo/tree/main/doc/data_types), with a Rails migration:
|
81
101
|
|
82
102
|
```ruby
|
83
103
|
class AddEncryptedBigintColumn < ActiveRecord::Migration[6.0]
|
84
104
|
def change
|
85
|
-
add_column :users, :
|
105
|
+
add_column :users, :date_of_birth, :enquo_date
|
86
106
|
end
|
87
107
|
end
|
88
108
|
```
|
89
109
|
|
110
|
+
Apply this migration in the usual fashion (`rails db:migrate`).
|
111
|
+
|
90
112
|
|
91
113
|
## Reading and Writing
|
92
114
|
|
93
|
-
You can now use that attribute in your models as you would normally.
|
94
|
-
For example, insert a new record:
|
115
|
+
You can now, without any further ado, use that attribute in your models as you would normally.
|
116
|
+
For example, you can insert a new record:
|
95
117
|
|
96
118
|
```ruby
|
97
|
-
User.create!([{name: "Clara Bloggs", username: "cbloggs",
|
119
|
+
User.create!([{name: "Clara Bloggs", username: "cbloggs", date_of_birth: Date.new(1970, 1, 1)}])
|
98
120
|
```
|
99
121
|
|
100
122
|
When you retrieve a record, the value is there for you to read:
|
101
123
|
|
102
124
|
```ruby
|
103
|
-
User.where(username: "cbloggs").first.
|
125
|
+
User.where(username: "cbloggs").first.date_of_birth.to_s # => "1970-01-01"
|
104
126
|
```
|
105
127
|
|
106
|
-
So far, nothing more spectacular than what AR Encryption will get you.
|
107
|
-
The fun begins now...
|
108
|
-
|
109
128
|
|
110
129
|
## Querying
|
111
130
|
|
112
|
-
|
131
|
+
This is where things get *neat*.
|
132
|
+
|
133
|
+
Performing a query on Enquo-encrypted data is done the same way as on unencrypted data.
|
134
|
+
|
135
|
+
You can query for records that have the exact value you're looking for:
|
113
136
|
|
114
137
|
```ruby
|
115
|
-
User.where(
|
138
|
+
User.where(date_of_birth: Date(1970, 1, 1))
|
116
139
|
```
|
117
140
|
|
118
|
-
Or you can query for
|
141
|
+
Or you can query for users born less than 50 years ago:
|
119
142
|
|
120
143
|
```ruby
|
121
|
-
|
122
|
-
User.where(age: User.enquo(:age, ...50))
|
144
|
+
User.where(date_of_birth: (Date.today - 50.years))..)
|
123
145
|
```
|
124
146
|
|
125
|
-
|
147
|
+
This doesn't seem so magical, until you take a peek in the database, and realise that *all the data is still encrypted*:
|
126
148
|
|
127
149
|
```sh
|
128
|
-
psql> SELECT
|
150
|
+
psql> SELECT date_of_birth FROM users WHERE username='cbloggs';
|
129
151
|
age
|
130
152
|
-------
|
131
|
-
{"
|
153
|
+
{"v1":{"a":[<lots of numbers>],"y":[<lots and LOTS of numbers>],<etc etc>}}
|
132
154
|
```
|
133
155
|
|
134
|
-
|
156
|
+
|
157
|
+
## Migrating Existing Data to Encrypted Form
|
158
|
+
|
159
|
+
This is a topic on which a lot of words can be written.
|
160
|
+
For the sake of tidiness, these words are in [a guide of their own](docs/MIGRATION.md).
|
135
161
|
|
136
162
|
|
137
163
|
## Indexing and Ordering
|
138
164
|
|
139
|
-
To maintain [security](https://enquo.org/about/threat-models#snapshot-security), ActiveEnquo
|
140
|
-
This is fine for many situations -- many columns don't need indexes.
|
165
|
+
To maintain [security by default](https://enquo.org/about/threat-models#snapshot-security), ActiveEnquo doesn't provide the ability to `ORDER BY` or index columns by default.
|
166
|
+
This is fine for many situations -- many columns don't need indexes or to be ordered in a query.
|
141
167
|
|
142
168
|
For those columns that *do* need indexes or `ORDER BY` support, you can enable support for them by setting the `enable_reduced_security_operations` flag on the attribute, like this:
|
143
169
|
|
@@ -148,10 +174,12 @@ class User < ApplicationRecord
|
|
148
174
|
end
|
149
175
|
```
|
150
176
|
|
151
|
-
|
177
|
+
|
178
|
+
### Security Considerations
|
152
179
|
|
153
180
|
As the name implies, "reduced security operations" require that the security of the data in the column be lower than [Enquo's default security properties](https://enquo.org/about/threat-models#snapshot-security).
|
154
|
-
|
181
|
+
Specifically, extra data needs to be stored in the value to enable indexing and ordering.
|
182
|
+
This extra data can be used by an attacker to:
|
155
183
|
|
156
184
|
* Identify all rows which have the same value for the column (although not what that value actually *is*); and
|
157
185
|
|
@@ -174,22 +202,14 @@ class User < ApplicationRecord
|
|
174
202
|
end
|
175
203
|
```
|
176
204
|
|
205
|
+
More accurate indications of the disk space requirements for the supported data types can be found in [the description of each data type](https://github.com/enquo/pg_enquo/tree/main/doc/data_types).
|
177
206
|
|
178
|
-
# Future Developments
|
179
207
|
|
180
|
-
|
181
|
-
|
182
|
-
* **Support for key rotation**: this isn't tricky, just a bit fiddly.
|
183
|
-
Encrypted values have a "key ID" associated with them, so we can find which values are out-of-date, but multiple keys need to useable for decryption.
|
184
|
-
Closely related to this is **support for renaming columns**, which is problematic because keys are derived based on the column name.
|
185
|
-
Again, key IDs (to find values encrypted with the previous column name) and the ability to attempt decryption with a key based on the previous column name sorts this out.
|
186
|
-
|
187
|
-
* **Strings**: a great deal of the sensitive data that needs protecting is in the form of strings.
|
188
|
-
Querying strings is somewhat more involved than numeric data, and so the means of encrypting strings such that they're queryable *and* secure are more complex.
|
189
|
-
Enquo cannot be a really useful library for supporting encrypted querying until at least some common string query operations are supported, though, so it is important that this be implemented.
|
208
|
+
# Future Developments
|
190
209
|
|
191
|
-
|
192
|
-
|
210
|
+
ActiveEnquo is far from finished.
|
211
|
+
Many more features are coming in the future.
|
212
|
+
See [the Enquo project roadmap](https://enquo.org/roadmap) for details of what we're still intending to implement.
|
193
213
|
|
194
214
|
|
195
215
|
# Contributing
|
data/active_enquo.gemspec
CHANGED
@@ -27,7 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
|
28
28
|
s.required_ruby_version = ">= 2.7.0"
|
29
29
|
|
30
|
-
s.add_runtime_dependency "enquo-core", "~> 0.
|
30
|
+
s.add_runtime_dependency "enquo-core", "~> 0.7"
|
31
31
|
s.add_runtime_dependency "activerecord", ">= 6"
|
32
32
|
|
33
33
|
s.add_development_dependency "bundler"
|
data/docs/MIGRATION.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
If you have data already in your database that you wish to protect using ActiveEnquo, you can migrate the data into an encrypted form.
|
2
|
+
|
3
|
+
There are two approaches that can be used:
|
4
|
+
|
5
|
+
* [Direct Migration](#data-migration-with-downtime), which is straightforward but involves application downtime; or
|
6
|
+
|
7
|
+
* [Live Migration](#live-data-migration), which is more complicated, but can be done while keeping the application available at all times.
|
8
|
+
|
9
|
+
|
10
|
+
# Data Migration (with Downtime)
|
11
|
+
|
12
|
+
If your application can withstand being down for a period of time, you can use a simple migration process that encrypts the data and modifies the application appropriate in a single pass.
|
13
|
+
It relies on their being a period with nothing accessing the column(s) being migrated, which usually means that the entire application (including background workers and periodic tasks) being shut down, before being restarted.
|
14
|
+
The total downtime will depend on how long it takes to encrypt and write all the data being migrated, which is mostly a function of the amount of data being stored.
|
15
|
+
|
16
|
+
|
17
|
+
## Step 1: Configure the Encrypted Column(s)
|
18
|
+
|
19
|
+
Create an ActiveRecord migration which renames the existing column and creates a new `enquo_*` column with the old name.
|
20
|
+
For example, if you already had a `date_of_birth` column, your migration would look like this:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
class EncryptUsersDateOfBirth < ActiveRecord::Migration[7.0]
|
24
|
+
def change
|
25
|
+
rename_column :users, :date_of_birth, :date_of_birth_plaintext
|
26
|
+
add_column :users, :date_of_birth, :enquo_date
|
27
|
+
User.enquo_encrypt_columns(date_of_birth_plaintext: :date_of_birth)
|
28
|
+
remove_column :users, :date_of_birth_plaintext
|
29
|
+
end
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
The `Model.encrypt_columns` method loads all the records in the table, and encrypts the value in the plaintext column and writes it to the corresponding encrypted column.
|
34
|
+
|
35
|
+
If you want to encrypt several columns in a single model, you can do so in a single migration, by renaming all the columns and adding `enquo_*` type columns, and then providing the mapping of all columns together in a single `Model.encrypt_columns` call.
|
36
|
+
This is the recommended approach, as it improves efficiency because the records only have to be loaded and saved once.
|
37
|
+
|
38
|
+
If you want to encrypt columns in multiple models in one downtime, just repeat the above steps for each table and model involved.
|
39
|
+
|
40
|
+
|
41
|
+
## Step 2: Modify Queries
|
42
|
+
|
43
|
+
When providing data to a query on an encrypted column, you need to make a call to `Model.enquo` in order to encrypt the value for querying.
|
44
|
+
|
45
|
+
To continue our `date_of_birth` example above, you need to find any queries that reference the `date_of_birth` column, and modify the code to pass the value for the `date_of_birth` column through a call to `User.enquo(:date_of_birth, <value>)`.
|
46
|
+
|
47
|
+
For a query that found all users with a date of birth equal to a query parameter, that originally looked like this:
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
User.where(date_of_birth: params[:dob])
|
51
|
+
```
|
52
|
+
|
53
|
+
You'd modify it to look like this, instead:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
User.where(date_of_birth: User.enquo(:date_of_birth, params[:dob]))
|
57
|
+
```
|
58
|
+
|
59
|
+
If the value for the query was passed in as a positional parameter, you just encrypt the value the same way, so that a query might look like this:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
User.where("date_of_birth > ? OR date_of_birth IS NULL", User.enquo(:date_of_birth, params[:dob]))
|
63
|
+
```
|
64
|
+
|
65
|
+
|
66
|
+
## Step 3: Deploy
|
67
|
+
|
68
|
+
Once the above changes are all made and committed to revision control, it's time to commence the downtime.
|
69
|
+
Shutdown all the application servers, background job workers, and anything else that accesses the database, then perform a normal deployment -- running the database migration process before starting the application again.
|
70
|
+
|
71
|
+
The migration may take some time to run, if the table is large.
|
72
|
+
|
73
|
+
|
74
|
+
## Step 4: Enjoy Fully Encrypted Data
|
75
|
+
|
76
|
+
The column(s) you migrated are now fully protected by Enquo's queryable encryption.
|
77
|
+
Relax and enjoy your preferred beverage in celebration!
|
78
|
+
|
79
|
+
|
80
|
+
# Live Data Migration
|
81
|
+
|
82
|
+
Converting the data in a column to be fully encrypted, while avoiding any application downtime, requires making several changes to the application and database schema in alternating sequence.
|
83
|
+
This is necessary to ensure that parts of the application stack running both older and newer code versions can work with the database schema in place at all times.
|
84
|
+
|
85
|
+
> # WORK IN PROGRESS
|
86
|
+
>
|
87
|
+
> This section has not been written out in detail.
|
88
|
+
> The short version is:
|
89
|
+
>
|
90
|
+
> 1. Rename the unencrypted column:
|
91
|
+
> 1. `create_column :users, :date_of_birth_plaintext, :date`
|
92
|
+
> 2. Modify the model to write changes to `date_of_birth` to `date_of_birth_plaintext` as well
|
93
|
+
> 3. Deploy
|
94
|
+
> 4. Migration to copy all `date_of_birth` values to `date_of_birth_plaintext`
|
95
|
+
> 5. Deploy
|
96
|
+
> 6. Modify app to read/query from `date_of_birth_plaintext`, add `date_of_birth` to `User.ignored_columns`
|
97
|
+
> 7. Deploy
|
98
|
+
> 8. `drop_column :users, :date_of_birth`
|
99
|
+
> 2. Create the encrypted column:
|
100
|
+
> 1. `create_column :users, :date_of_birth, :enquo_date`
|
101
|
+
> 2. Modify the model to write changes to `date_of_birth_plaintext` to `date_of_birth` as well, remove `date_of_birth` from `User.ignored_columns`
|
102
|
+
> 3. Deploy
|
103
|
+
> 4. Migration to encrypt all `date_of_birth_plaintext` values into `date_of_birth`
|
104
|
+
> 5. Deploy
|
105
|
+
> 6. Modify app to read/encrypted query from `date_of_birth`, add `date_of_birth_plaintext` to `User.ignored_columns`
|
106
|
+
> 7. Deploy
|
107
|
+
> 8. `drop_column :users, :date_of_birth_plaintext`
|
@@ -0,0 +1 @@
|
|
1
|
+
/.pgdbenv
|
@@ -0,0 +1,27 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "active_enquo"
|
5
|
+
require "pg"
|
6
|
+
|
7
|
+
require_relative "../init"
|
8
|
+
|
9
|
+
def assert_eq(expected, actual)
|
10
|
+
unless expected == actual
|
11
|
+
$stderr.puts "Expected #{expected.inspect} to equal #{actual.inspect}"
|
12
|
+
exit 1
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def value(f, v)
|
17
|
+
if ENV.key?("USING_ENQUO")
|
18
|
+
People.enquo(f, v)
|
19
|
+
else
|
20
|
+
v
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
ActiveRecord::Base.establish_connection(adapter: ENV.fetch("DBTYPE"))
|
25
|
+
|
26
|
+
assert_eq(["Meyers"], People.where(first_name: value(:first_name, "Seth")).all.map { |p| p.last_name })
|
27
|
+
assert_eq(8, People.where(date_of_birth: value(:date_of_birth, "1980-01-01")..).count)
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class EncryptPeopleData < ActiveRecord::Migration[ENV.fetch("AR_VERSION", "7.0").to_f]
|
2
|
+
def up
|
3
|
+
rename_column :people, :first_name, :first_name_plaintext
|
4
|
+
rename_column :people, :last_name, :last_name_plaintext
|
5
|
+
rename_column :people, :date_of_birth, :date_of_birth_plaintext
|
6
|
+
|
7
|
+
add_column :people, :first_name, :enquo_text
|
8
|
+
add_column :people, :last_name, :enquo_text
|
9
|
+
add_column :people, :date_of_birth, :enquo_date
|
10
|
+
|
11
|
+
People.enquo_encrypt_columns(
|
12
|
+
{
|
13
|
+
first_name_plaintext: :first_name,
|
14
|
+
last_name_plaintext: :last_name,
|
15
|
+
date_of_birth_plaintext: :date_of_birth,
|
16
|
+
},
|
17
|
+
# Smol batch size exercises the batching functionality
|
18
|
+
batch_size: 5
|
19
|
+
)
|
20
|
+
|
21
|
+
remove_column :people, :first_name_plaintext
|
22
|
+
remove_column :people, :last_name_plaintext
|
23
|
+
remove_column :people, :date_of_birth_plaintext
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -euo pipefail
|
4
|
+
|
5
|
+
. ../helper.sh
|
6
|
+
|
7
|
+
export DBTYPE="postgresql"
|
8
|
+
|
9
|
+
check_enquo_pg_db
|
10
|
+
clear_pg_db
|
11
|
+
|
12
|
+
ar_db_migrate "001"
|
13
|
+
load_people
|
14
|
+
|
15
|
+
./exercise_model
|
16
|
+
|
17
|
+
ar_db_migrate "002"
|
18
|
+
USING_ENQUO="y" ./exercise_model
|
data/e2e_tests/helper.sh
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
if [ -f "../.pgdbenv" ]; then
|
2
|
+
. ../.pgdbenv
|
3
|
+
fi
|
4
|
+
|
5
|
+
check_enquo_pg_db() {
|
6
|
+
if [ "$(psql -tAc "select count(extname) FROM pg_catalog.pg_extension WHERE extname='pg_enquo'")" != "1" ]; then
|
7
|
+
echo "Specified PostgreSQL database does not have the pg_enquo extension." >&2
|
8
|
+
echo "Check your PG* env vars for correctness, and install the extension if needed." >&2
|
9
|
+
exit 1
|
10
|
+
fi
|
11
|
+
}
|
12
|
+
|
13
|
+
clear_pg_db() {
|
14
|
+
for tbl in $(psql -tAc "select relname from pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace where c.relkind='r' and n.nspname = 'public'"); do
|
15
|
+
psql -c "DROP TABLE $tbl" >/dev/null
|
16
|
+
done
|
17
|
+
for seq in $(psql -tAc "select relname from pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n on n.oid = c.relnamespace where c.relkind='S' and n.nspname = 'public'"); do
|
18
|
+
psql -c "DROP SEQUENCE $seq" >/dev/null
|
19
|
+
done
|
20
|
+
}
|
21
|
+
|
22
|
+
run_ruby() {
|
23
|
+
ruby -r ../init "$@"
|
24
|
+
}
|
25
|
+
|
26
|
+
ar_db_migrate() {
|
27
|
+
local target_version="$1"
|
28
|
+
|
29
|
+
run_ruby -e "ActiveRecord::MigrationContext.new(['migrations']).up($target_version)" >/dev/null
|
30
|
+
}
|
31
|
+
|
32
|
+
load_people() {
|
33
|
+
run_ruby -e "People.create!(JSON.parse(File.read('../people.json')))"
|
34
|
+
}
|
data/e2e_tests/init.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "active_enquo"
|
3
|
+
|
4
|
+
ActiveEnquo.root_key = Enquo::RootKey::Static.new("f91c5017a2d946403cc90a688266ff32d186aa2a00efd34dcaa86be802e179d0")
|
5
|
+
|
6
|
+
DBTYPE = ENV.fetch("DBTYPE")
|
7
|
+
|
8
|
+
case DBTYPE
|
9
|
+
when "postgresql"
|
10
|
+
require "pg"
|
11
|
+
else
|
12
|
+
raise "Unsupported DBTYPE: #{DBTYPE.inspect}"
|
13
|
+
end
|
14
|
+
|
15
|
+
class People < ActiveRecord::Base
|
16
|
+
end
|
17
|
+
|
18
|
+
ActiveRecord::Base.establish_connection(adapter: DBTYPE)
|
@@ -0,0 +1 @@
|
|
1
|
+
[{"first_name":"Sergio","last_name":"Leone","date_of_birth":"1929-01-03"},{"first_name":"Bradley","last_name":"Cooper","date_of_birth":"1975-01-05"},{"first_name":"Robert","last_name":"Duvall","date_of_birth":"1931-01-05"},{"first_name":"Kate","last_name":"Moss","date_of_birth":"1974-01-16"},{"first_name":"Laura","last_name":"Schlessinger","date_of_birth":"1947-01-16"},{"first_name":"Jason","last_name":"Segel","date_of_birth":"1980-01-18"},{"first_name":"Pete","last_name":"Buttigieg","date_of_birth":"1982-01-19"},{"first_name":"Mischa","last_name":"Barton","date_of_birth":"1986-01-24"},{"first_name":"Shirley","last_name":"Mason","date_of_birth":"1923-01-25"},{"first_name":"Boris","last_name":"Yeltsin","date_of_birth":"1931-02-01"},{"first_name":"Tom","last_name":"Wilkinson","date_of_birth":"1948-02-05"},{"first_name":"Tony","last_name":"Iommi","date_of_birth":"1948-02-19"},{"first_name":"Sri","last_name":"Srinivasan","date_of_birth":"1967-02-23"},{"first_name":"Tony","last_name":"Randall","date_of_birth":"1920-02-26"},{"first_name":"David","last_name":"Blaine","date_of_birth":"1973-04-04"},{"first_name":"Muddy","last_name":"Waters","date_of_birth":"1915-04-04"},{"first_name":"Danny","last_name":"Almonte","date_of_birth":"1987-04-07"},{"first_name":"Meghann","last_name":"Shaughnessy","date_of_birth":"1979-04-13"},{"first_name":"Maisie","last_name":"Williams","date_of_birth":"1997-04-15"},{"first_name":"Immanuel","last_name":"Kant","date_of_birth":"1724-04-22"},{"first_name":"John","last_name":"Oliver","date_of_birth":"1977-04-23"},{"first_name":"Penelope","last_name":"Cruz","date_of_birth":"1974-04-28"},{"first_name":"Anne","last_name":"Parillaud","date_of_birth":"1961-05-06"},{"first_name":"Cate","last_name":"Blanchett","date_of_birth":"1969-05-14"},{"first_name":"Tucker","last_name":"Carlson","date_of_birth":"1969-05-16"},{"first_name":"Nancy","last_name":"Kwan","date_of_birth":"1939-05-19"},{"first_name":"Melissa","last_name":"Etheridge","date_of_birth":"1961-05-29"},{"first_name":"Marissa","last_name":"Mayer","date_of_birth":"1975-05-30"},{"first_name":"Marvin","last_name":"Hamlisch","date_of_birth":"1944-06-02"},{"first_name":"Gwendolyn","last_name":"Brooks","date_of_birth":"1917-06-07"},{"first_name":"Paul","last_name":"Lynde","date_of_birth":"1927-06-13"},{"first_name":"Paul","last_name":"McCartney","date_of_birth":"1942-06-18"},{"first_name":"Susan","last_name":"Hayward","date_of_birth":"1917-06-30"},{"first_name":"Léa","last_name":"Seydoux","date_of_birth":"1985-07-01"},{"first_name":"Amanda","last_name":"Knox","date_of_birth":"1987-07-09"},{"first_name":"Cindy","last_name":"Sheehan","date_of_birth":"1957-07-10"},{"first_name":"Bill","last_name":"Cosby","date_of_birth":"1937-07-12"},{"first_name":"Raymond","last_name":"Chandler","date_of_birth":"1888-07-23"},{"first_name":"Christopher","last_name":"Nolan","date_of_birth":"1970-07-30"},{"first_name":"Melanie","last_name":"Griffith","date_of_birth":"1957-08-09"},{"first_name":"Freddie","last_name":"Gray","date_of_birth":"1989-08-16"},{"first_name":"Steve","last_name":"Case","date_of_birth":"1958-08-21"},{"first_name":"Cal","last_name":"Ripken","date_of_birth":"1960-08-24"},{"first_name":"Regis","last_name":"Philbin","date_of_birth":"1931-08-25"},{"first_name":"Shania","last_name":"Twain","date_of_birth":"1965-08-28"},{"first_name":"Adam","last_name":"Curry","date_of_birth":"1964-09-03"},{"first_name":"Rachel","last_name":"Hunter","date_of_birth":"1969-09-09"},{"first_name":"Bashar","last_name":"al-Assad","date_of_birth":"1965-09-11"},{"first_name":"Amy","last_name":"Poehler","date_of_birth":"1971-09-16"},{"first_name":"Jim","last_name":"Thompson","date_of_birth":"1906-09-27"},{"first_name":"Matt","last_name":"Damon","date_of_birth":"1970-10-08"},{"first_name":"Leopold","last_name":"Senghor","date_of_birth":"1906-10-09"},{"first_name":"William","last_name":"Penn","date_of_birth":"1644-10-14"},{"first_name":"Rebecca","last_name":"Romijn","date_of_birth":"1971-11-06"},{"first_name":"Auguste","last_name":"Rodin","date_of_birth":"1840-11-12"},{"first_name":"Nate","last_name":"Parker","date_of_birth":"1979-11-18"},{"first_name":"Larry","last_name":"Bird","date_of_birth":"1956-12-07"},{"first_name":"Bobby","last_name":"Flay","date_of_birth":"1964-12-10"},{"first_name":"Chris","last_name":"Evert","date_of_birth":"1954-12-21"},{"first_name":"Frank","last_name":"Zappa","date_of_birth":"1940-12-21"},{"first_name":"Estella","last_name":"Warren","date_of_birth":"1978-12-23"},{"first_name":"Carlos","last_name":"Castaneda","date_of_birth":"1925-12-25"},{"first_name":"Seth","last_name":"Meyers","date_of_birth":"1973-12-28"}]
|
data/e2e_tests/run
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -euo pipefail
|
4
|
+
|
5
|
+
cd "$(dirname "${BASH_SOURCE[0]}")"
|
6
|
+
|
7
|
+
for t in [0-9]*; do
|
8
|
+
rv="0"
|
9
|
+
cd "$t"
|
10
|
+
|
11
|
+
./run || rv="$?"
|
12
|
+
|
13
|
+
if [ "$rv" != "0" ]; then
|
14
|
+
echo "Test $i failed with exit code $rv" >&2
|
15
|
+
exit 1
|
16
|
+
fi
|
17
|
+
done
|
18
|
+
|
19
|
+
echo "All tests passed."
|
data/lib/active_enquo.rb
CHANGED
@@ -21,7 +21,57 @@ module ActiveEnquo
|
|
21
21
|
RootKey = Enquo::RootKey
|
22
22
|
|
23
23
|
module ActiveRecord
|
24
|
-
module
|
24
|
+
module QueryFilterMangler
|
25
|
+
class Ciphertext < String
|
26
|
+
end
|
27
|
+
private_constant :Ciphertext
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def mangle_query_filter(a)
|
32
|
+
args = a.first
|
33
|
+
if args.is_a?(Hash)
|
34
|
+
args.keys.each do |attr|
|
35
|
+
next unless enquo_attr?(attr)
|
36
|
+
|
37
|
+
if args[attr].is_a?(Array)
|
38
|
+
args[attr] = args[attr].map { |v| maybe_enquo(attr, v) }
|
39
|
+
elsif args[attr].is_a?(Range)
|
40
|
+
r = args[attr]
|
41
|
+
args[attr] = if r.exclude_end?
|
42
|
+
if r.begin.nil?
|
43
|
+
...maybe_enquo(attr, r.end)
|
44
|
+
elsif r.end.nil?
|
45
|
+
(maybe_enquo(attr.r.begin)...)
|
46
|
+
else
|
47
|
+
maybe_enquo(attr.r.begin)...maybe_enquo(attr, r.end)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
if r.begin.nil?
|
51
|
+
..maybe_enquo(attr, r.end)
|
52
|
+
elsif r.end.nil?
|
53
|
+
maybe_enquo(attr.r.begin)..
|
54
|
+
else
|
55
|
+
maybe_enquo(attr.r.begin)..maybe_enquo(attr, r.end)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
args[attr] = maybe_enquo(attr, args[attr])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def maybe_enquo(attr, v)
|
66
|
+
if v.nil? || v.is_a?(Ciphertext) || v.is_a?(::ActiveRecord::StatementCache::Substitute)
|
67
|
+
v
|
68
|
+
else
|
69
|
+
Ciphertext.new(enquo(attr, v))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module BaseExtension
|
25
75
|
extend ActiveSupport::Concern
|
26
76
|
|
27
77
|
def _read_attribute(attr_name, &block)
|
@@ -29,6 +79,7 @@ module ActiveEnquo
|
|
29
79
|
if t.is_a?(::ActiveEnquo::Type)
|
30
80
|
relation = self.class.arel_table.name
|
31
81
|
value = @attributes.fetch_value(attr_name, &block)
|
82
|
+
return nil if value.nil?
|
32
83
|
field = ::ActiveEnquo.root.field(relation, attr_name)
|
33
84
|
begin
|
34
85
|
t.decrypt(value, @attributes.fetch_value(@primary_key).to_s, field)
|
@@ -50,10 +101,7 @@ module ActiveEnquo
|
|
50
101
|
relation = self.class.arel_table.name
|
51
102
|
field = ::ActiveEnquo.root.field(relation, attr_name)
|
52
103
|
attr_opts = self.class.enquo_attribute_options.fetch(attr_name.to_sym, {})
|
53
|
-
|
54
|
-
:unsafe
|
55
|
-
end
|
56
|
-
db_value = t.encrypt(value, @attributes.fetch_value(@primary_key).to_s, field, safety: safety, no_query: attr_opts[:no_query])
|
104
|
+
db_value = t.encrypt(value, @attributes.fetch_value(@primary_key).to_s, field, **attr_opts)
|
57
105
|
@attributes.write_from_user(attr_name, db_value)
|
58
106
|
else
|
59
107
|
super
|
@@ -61,31 +109,148 @@ module ActiveEnquo
|
|
61
109
|
end
|
62
110
|
|
63
111
|
module ClassMethods
|
64
|
-
|
112
|
+
include QueryFilterMangler
|
113
|
+
|
114
|
+
def find_by(*a)
|
115
|
+
mangle_query_filter(a)
|
116
|
+
super
|
117
|
+
end
|
118
|
+
|
119
|
+
def enquo(attr_name, value_or_meta_id, maybe_value = nil)
|
120
|
+
meta_id, value = if value_or_meta_id.is_a?(Symbol)
|
121
|
+
[value_or_meta_id, maybe_value]
|
122
|
+
else
|
123
|
+
[nil, value_or_meta_id]
|
124
|
+
end
|
125
|
+
|
65
126
|
t = self.attribute_types[attr_name.to_s]
|
66
127
|
if t.is_a?(::ActiveEnquo::Type)
|
67
128
|
relation = self.arel_table.name
|
68
129
|
field = ::ActiveEnquo.root.field(relation, attr_name)
|
69
|
-
|
130
|
+
if meta_id.nil?
|
131
|
+
t.encrypt(value, "", field, enable_reduced_security_operations: true)
|
132
|
+
else
|
133
|
+
t.encrypt_metadata_value(meta_id, value, field)
|
134
|
+
end
|
70
135
|
else
|
71
136
|
raise ArgumentError, "Cannot produce encrypted value on a non-enquo attribute '#{attr_name}'"
|
72
137
|
end
|
73
138
|
end
|
74
139
|
|
140
|
+
def unenquo(attr_name, value, ctx)
|
141
|
+
t = self.attribute_types[attr_name.to_s]
|
142
|
+
if t.is_a?(::ActiveEnquo::Type)
|
143
|
+
relation = self.arel_table.name
|
144
|
+
field = ::ActiveEnquo.root.field(relation, attr_name)
|
145
|
+
begin
|
146
|
+
t.decrypt(value, ctx, field)
|
147
|
+
rescue Enquo::Error
|
148
|
+
t.decrypt(value, "", field)
|
149
|
+
end
|
150
|
+
else
|
151
|
+
raise ArgumentError, "Cannot decrypt value on a non-enquo attribute '#{attr_name}'"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def enquo_attr?(attr_name)
|
156
|
+
self.attribute_types[attr_name.to_s].is_a?(::ActiveEnquo::Type)
|
157
|
+
end
|
158
|
+
|
75
159
|
def enquo_attr(attr_name, opts)
|
160
|
+
if opts.key?(:default)
|
161
|
+
default_value = opts.delete(:default)
|
162
|
+
after_initialize do
|
163
|
+
next if persisted?
|
164
|
+
next unless self.send(attr_name).nil?
|
165
|
+
self.send(:"#{attr_name}=", default_value.duplicable? ? default_value.dup : default_value)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
76
169
|
enquo_attribute_options[attr_name] = @enquo_attribute_options[attr_name].merge(opts)
|
77
170
|
end
|
78
171
|
|
79
172
|
def enquo_attribute_options
|
80
173
|
@enquo_attribute_options ||= Hash.new({})
|
81
174
|
end
|
175
|
+
|
176
|
+
def enquo_encrypt_columns(column_map, batch_size: 10_000)
|
177
|
+
plaintext_columns = column_map.keys
|
178
|
+
relation = self.arel_table.name
|
179
|
+
in_progress = true
|
180
|
+
self.reset_column_information
|
181
|
+
|
182
|
+
while in_progress
|
183
|
+
self.transaction do
|
184
|
+
# The .where("0=1") here is a dummy condition so that the q.or in the .each will work properly
|
185
|
+
q = self.select(self.primary_key).select(plaintext_columns).where("0=1")
|
186
|
+
column_map.each do |pt_col, ct_col|
|
187
|
+
q = q.or(self.where(ct_col => nil).where.not(pt_col => nil))
|
188
|
+
end
|
189
|
+
|
190
|
+
q = q.limit(batch_size).lock
|
191
|
+
|
192
|
+
rows = ::ActiveRecord::Base.connection.exec_query(q.to_sql)
|
193
|
+
if rows.length == 0
|
194
|
+
in_progress = false
|
195
|
+
else
|
196
|
+
rows.each do |row|
|
197
|
+
values = Hash[column_map.map do |pt_col, ct_col|
|
198
|
+
field = ::ActiveEnquo.root.field(relation, ct_col)
|
199
|
+
attr_opts = self.enquo_attribute_options.fetch(ct_col.to_sym, {})
|
200
|
+
t = self.attribute_types[ct_col.to_s]
|
201
|
+
db_value = t.encrypt(row[pt_col.to_s], row[self.primary_key].to_s, field, **attr_opts)
|
202
|
+
|
203
|
+
[ct_col, db_value]
|
204
|
+
end]
|
205
|
+
|
206
|
+
self.where(self.primary_key => row[self.primary_key]).update_all(values)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
module TableDefinitionExtension
|
216
|
+
def enquo_boolean(name, **options)
|
217
|
+
column(name, :enquo_boolean, **options)
|
218
|
+
end
|
219
|
+
|
220
|
+
def enquo_bigint(name, **options)
|
221
|
+
column(name, :enquo_bigint, **options)
|
222
|
+
end
|
223
|
+
|
224
|
+
def enquo_date(name, **options)
|
225
|
+
column(name, :enquo_date, **options)
|
226
|
+
end
|
227
|
+
|
228
|
+
def enquo_text(name, **options)
|
229
|
+
column(name, :enquo_text, **options)
|
82
230
|
end
|
83
231
|
end
|
232
|
+
|
233
|
+
module RelationExtension
|
234
|
+
include QueryFilterMangler
|
235
|
+
extend ActiveSupport::Concern
|
236
|
+
|
237
|
+
def where(*a)
|
238
|
+
mangle_query_filter(a)
|
239
|
+
super
|
240
|
+
end
|
241
|
+
|
242
|
+
def exists?(*a)
|
243
|
+
mangle_query_filter(a)
|
244
|
+
super
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|
84
248
|
end
|
85
249
|
|
86
250
|
module Postgres
|
87
251
|
module ConnectionAdapter
|
88
252
|
def initialize_type_map(m = type_map)
|
253
|
+
m.register_type "enquo_boolean", ActiveEnquo::Type::Boolean.new
|
89
254
|
m.register_type "enquo_bigint", ActiveEnquo::Type::Bigint.new
|
90
255
|
m.register_type "enquo_date", ActiveEnquo::Type::Date.new
|
91
256
|
m.register_type "enquo_text", ActiveEnquo::Type::Text.new
|
@@ -96,13 +261,35 @@ module ActiveEnquo
|
|
96
261
|
end
|
97
262
|
|
98
263
|
class Type < ::ActiveRecord::Type::Value
|
264
|
+
class Boolean < Type
|
265
|
+
def type
|
266
|
+
:enquo_boolean
|
267
|
+
end
|
268
|
+
|
269
|
+
def encrypt(value, context, field, enable_reduced_security_operations: false, no_query: false)
|
270
|
+
if value.nil? || value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
271
|
+
value
|
272
|
+
else
|
273
|
+
field.encrypt_boolean(value, context, safety: enable_reduced_security_operations ? :unsafe : true, no_query: no_query)
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def decrypt(value, context, field)
|
278
|
+
field.decrypt_boolean(value, context)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
99
282
|
class Bigint < Type
|
100
283
|
def type
|
101
284
|
:enquo_bigint
|
102
285
|
end
|
103
286
|
|
104
|
-
def encrypt(value, context, field,
|
105
|
-
|
287
|
+
def encrypt(value, context, field, enable_reduced_security_operations: false, no_query: false)
|
288
|
+
if value.nil? || value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
289
|
+
value
|
290
|
+
else
|
291
|
+
field.encrypt_i64(value, context, safety: enable_reduced_security_operations ? :unsafe : true, no_query: no_query)
|
292
|
+
end
|
106
293
|
end
|
107
294
|
|
108
295
|
def decrypt(value, context, field)
|
@@ -115,9 +302,13 @@ module ActiveEnquo
|
|
115
302
|
:enquo_date
|
116
303
|
end
|
117
304
|
|
118
|
-
def encrypt(value, context, field,
|
305
|
+
def encrypt(value, context, field, enable_reduced_security_operations: false, no_query: false)
|
119
306
|
value = cast_to_date(value)
|
120
|
-
|
307
|
+
if value.nil?
|
308
|
+
value
|
309
|
+
else
|
310
|
+
field.encrypt_date(value, context, safety: enable_reduced_security_operations ? :unsafe : true, no_query: no_query)
|
311
|
+
end
|
121
312
|
end
|
122
313
|
|
123
314
|
def decrypt(value, context, field)
|
@@ -131,6 +322,8 @@ module ActiveEnquo
|
|
131
322
|
value
|
132
323
|
elsif value.respond_to?(:to_date)
|
133
324
|
value.to_date
|
325
|
+
elsif value.nil? || (value.respond_to?(:empty?) && value.empty?) || value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
326
|
+
nil
|
134
327
|
else
|
135
328
|
Time.parse(value.to_s).to_date
|
136
329
|
end
|
@@ -142,26 +335,73 @@ module ActiveEnquo
|
|
142
335
|
:enquo_text
|
143
336
|
end
|
144
337
|
|
145
|
-
def encrypt(value, context, field,
|
146
|
-
|
338
|
+
def encrypt(value, context, field, enable_reduced_security_operations: false, no_query: false, enable_ordering: false)
|
339
|
+
if enable_ordering && !enable_reduced_security_operations
|
340
|
+
raise ArgumentError, "Cannot enable ordering on an Enquo attribute unless Reduced Security Operations are enabled"
|
341
|
+
end
|
342
|
+
|
343
|
+
if value.nil? || value.is_a?(::ActiveRecord::StatementCache::Substitute)
|
344
|
+
value
|
345
|
+
else
|
346
|
+
field.encrypt_text(value.respond_to?(:encode) ? value.encode("UTF-8") : value, context, safety: enable_reduced_security_operations ? :unsafe : true, no_query: no_query, order_prefix_length: enable_ordering ? 8 : nil)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def encrypt_metadata_value(name, value, field)
|
351
|
+
case name
|
352
|
+
when :length
|
353
|
+
field.encrypt_text_length_query(value)
|
354
|
+
else
|
355
|
+
raise ArgumentError, "Unknown metadata name for Text field: #{name.inspect}"
|
356
|
+
end
|
147
357
|
end
|
148
358
|
|
149
359
|
def decrypt(value, context, field)
|
150
|
-
|
360
|
+
if value.nil?
|
361
|
+
nil
|
362
|
+
else
|
363
|
+
field.decrypt_text(value, context)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
if defined?(Rails::Railtie)
|
370
|
+
class Initializer < Rails::Railtie
|
371
|
+
initializer "active_enquo.root_key" do |app|
|
372
|
+
if app
|
373
|
+
if app.credentials
|
374
|
+
if app.credentials.active_enquo
|
375
|
+
if root_key = app.credentials.active_enquo.root_key
|
376
|
+
ActiveEnquo.root_key = Enquo::RootKey::Static.new(root_key)
|
377
|
+
else
|
378
|
+
Rails.logger.warn "Could not initialize ActiveEnquo, as no active_enquo.root_key credential was found for this environment"
|
379
|
+
end
|
380
|
+
else
|
381
|
+
Rails.logger.warn "Could not initialize ActiveEnquo, as no active_enquo credentials were found for this environment"
|
382
|
+
end
|
383
|
+
else
|
384
|
+
Rails.logger.warn "Could not initialize ActiveEnquo, as no credentials were found for this environment"
|
385
|
+
end
|
386
|
+
else
|
387
|
+
Rails.logger.warn "Could not initialize ActiveEnquo, as no app was found for this environment"
|
388
|
+
end
|
151
389
|
end
|
152
390
|
end
|
153
391
|
end
|
154
392
|
end
|
155
393
|
|
156
394
|
ActiveSupport.on_load(:active_record) do
|
157
|
-
::ActiveRecord::
|
395
|
+
::ActiveRecord::Relation.prepend ActiveEnquo::ActiveRecord::RelationExtension
|
396
|
+
::ActiveRecord::Base.include ActiveEnquo::ActiveRecord::BaseExtension
|
397
|
+
|
398
|
+
::ActiveRecord::ConnectionAdapters::Table.include ActiveEnquo::ActiveRecord::TableDefinitionExtension
|
399
|
+
::ActiveRecord::ConnectionAdapters::TableDefinition.include ActiveEnquo::ActiveRecord::TableDefinitionExtension
|
158
400
|
|
159
401
|
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend ActiveEnquo::Postgres::ConnectionAdapter
|
160
|
-
# ::ActiveRecord::Type.register(:enquo_bigint, ActiveEnquo::Type::Bigint, adapter: :postgresql)
|
161
402
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
403
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_boolean] = { name: "enquo_boolean" }
|
404
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_bigint] = { name: "enquo_bigint" }
|
405
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_date] = { name: "enquo_date" }
|
406
|
+
::ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::NATIVE_DATABASE_TYPES[:enquo_text] = { name: "enquo_text" }
|
167
407
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_enquo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Palmer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: enquo-core
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.7'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.7'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: activerecord
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -195,6 +195,16 @@ files:
|
|
195
195
|
- README.md
|
196
196
|
- active_enquo.gemspec
|
197
197
|
- docs/DEVELOPMENT.md
|
198
|
+
- docs/MIGRATION.md
|
199
|
+
- e2e_tests/.gitignore
|
200
|
+
- e2e_tests/001_direct_migration/exercise_model
|
201
|
+
- e2e_tests/001_direct_migration/migrations/001_create_people_table.rb
|
202
|
+
- e2e_tests/001_direct_migration/migrations/002_encrypt_people_data.rb
|
203
|
+
- e2e_tests/001_direct_migration/run
|
204
|
+
- e2e_tests/helper.sh
|
205
|
+
- e2e_tests/init.rb
|
206
|
+
- e2e_tests/people.json
|
207
|
+
- e2e_tests/run
|
198
208
|
- lib/active_enquo.rb
|
199
209
|
homepage: https://enquo.org
|
200
210
|
licenses: []
|