pod4 0.10.2 → 0.10.3
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/.hgtags +1 -0
- data/lib/pod4/encrypting.rb +15 -4
- data/lib/pod4/version.rb +1 -1
- data/spec/common/model_plus_encrypting_spec.rb +7 -3
- data/spec/fixtures/database.rb +5 -0
- data/spec/jruby/sequel_encrypting_jdbc_pg_spec.rb +80 -0
- data/spec/mri/pg_encrypting_spec.rb +87 -0
- data/spec/mri/pg_interface_spec.rb.orig +611 -0
- data/spec/mri/sequel_encrypting_spec.rb +81 -0
- data/spec/mri/tds_encrypting_spec.rb +98 -0
- metadata +11 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c65450007521cb6a9e312bdc47350624c85f381f
|
4
|
+
data.tar.gz: 37a401097db2e57f2ea96c337e4b3e151a154606
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c7c157f8b35a9961a48e9457e7aa328dd1a4c361876aa236cdfbe009448a21e005c659fa7a089c79f4e61d169580d622e5539d3b04cf4d78f293422b0e20dc8e
|
7
|
+
data.tar.gz: 5be81a188a0e925fdbb45cb4f1bb74240eed34b16c6e103ce75905aefe3bc378354c744cf8437fca0389b1c68b82ef2f6fdb21b0b14a26672babcb133838c6e4
|
data/.hgtags
CHANGED
data/lib/pod4/encrypting.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "openssl"
|
2
|
+
require "base64"
|
2
3
|
require "pod4/errors"
|
3
4
|
require "pod4/metaxing"
|
4
5
|
|
@@ -137,8 +138,9 @@ module Pod4
|
|
137
138
|
# If the IV is not set we need to set it both in the model object AND the hash, since we've
|
138
139
|
# already obtained the hash from the model object.
|
139
140
|
if use_iv? && encryption_iv.nil?
|
140
|
-
|
141
|
-
|
141
|
+
iv = cipher.random_iv
|
142
|
+
set_encryption_iv(iv)
|
143
|
+
hash[self.class.encryption_iv_column] = Base64.strict_encode64(iv)
|
142
144
|
end
|
143
145
|
|
144
146
|
self.class.encryption_columns.each do |col|
|
@@ -154,10 +156,15 @@ module Pod4
|
|
154
156
|
def map_to_model(ot)
|
155
157
|
hash = ot.to_h
|
156
158
|
cipher = get_cipher(:decrypt)
|
157
|
-
|
159
|
+
|
160
|
+
# The IV is not in columns, we need to de-base-64 it and set it on the model ourselves
|
161
|
+
if use_iv?
|
162
|
+
iv = Base64.strict_decode64 hash[self.class.encryption_iv_column]
|
163
|
+
set_encryption_iv(iv)
|
164
|
+
end
|
158
165
|
|
159
166
|
self.class.encryption_columns.each do |col|
|
160
|
-
hash[col] = crypt(cipher, :decrypt,
|
167
|
+
hash[col] = crypt(cipher, :decrypt, encryption_iv, hash[col])
|
161
168
|
end
|
162
169
|
|
163
170
|
super Octothorpe.new(hash)
|
@@ -212,9 +219,13 @@ module Pod4
|
|
212
219
|
cipher.key = self.class.encryption_key
|
213
220
|
cipher.iv = iv if use_iv?
|
214
221
|
|
222
|
+
string = Base64.strict_decode64(string) if direction == :decrypt
|
223
|
+
|
215
224
|
answer = ""
|
216
225
|
answer << cipher.update(string.to_s) unless direction == :encrypt && string.empty?
|
217
226
|
answer << cipher.final
|
227
|
+
|
228
|
+
answer = Base64.strict_encode64(answer) if direction == :encrypt
|
218
229
|
answer
|
219
230
|
|
220
231
|
rescue OpenSSL::Cipher::CipherError
|
data/lib/pod4/version.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "date"
|
2
2
|
require "openssl"
|
3
|
+
require "base64"
|
3
4
|
require "octothorpe"
|
4
5
|
|
5
6
|
require "pod4"
|
@@ -19,7 +20,8 @@ describe "(Model with Encryption)" do
|
|
19
20
|
cipher.key = key
|
20
21
|
cipher.iv = iv if iv
|
21
22
|
|
22
|
-
(plaintext.empty? ? "" : cipher.update(plaintext) ) + cipher.final
|
23
|
+
answer = (plaintext.empty? ? "" : cipher.update(plaintext) ) + cipher.final
|
24
|
+
Base64.strict_encode64(answer)
|
23
25
|
end
|
24
26
|
|
25
27
|
let(:encryption_key) { "dflkasdgklajndgnalkghlgasdgasdghaalsdg" }
|
@@ -197,8 +199,10 @@ describe "(Model with Encryption)" do
|
|
197
199
|
it "writes an IV to the nonce field" do
|
198
200
|
m40.create
|
199
201
|
expect( m40_record.>>.nonce ).not_to be_nil
|
200
|
-
|
201
|
-
|
202
|
+
|
203
|
+
recordiv = Base64.strict_decode64(m40_record.>>.nonce)
|
204
|
+
expect( recordiv ).to eq m40.nonce
|
205
|
+
expect( recordiv ).to eq m40.encryption_iv
|
202
206
|
end
|
203
207
|
|
204
208
|
it "scrambles the encrypted columns and leaves the others alone" do
|
data/spec/fixtures/database.rb
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
require "pod4"
|
2
|
+
require "pod4/sequel_interface"
|
3
|
+
require "pod4/encrypting"
|
4
|
+
require "sequel"
|
5
|
+
require "jdbc/postgres"
|
6
|
+
|
7
|
+
|
8
|
+
describe "(writing encrypted data via sequel_interface)" do
|
9
|
+
|
10
|
+
def db_setup(connection)
|
11
|
+
connection.run(%Q| drop table if exists medical;
|
12
|
+
create table medical (
|
13
|
+
id serial primary key,
|
14
|
+
nonce text,
|
15
|
+
name text,
|
16
|
+
ailment text );| )
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def db_truncate(connection)
|
21
|
+
connection.run(%Q| truncate table medical restart identity; |)
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
before(:all) do
|
26
|
+
@Connection = Sequel.connect('jdbc:postgresql://centos7andy/pod4_test?user=pod4test&password=pod4test')
|
27
|
+
db_setup(@connection)
|
28
|
+
end
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
db_truncate(@connection)
|
32
|
+
medical_model_class.set_interface medical_interface_class.new(@connection)
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
let(:medical_interface_class) do
|
37
|
+
Class.new Pod4::SequelInterface do
|
38
|
+
set_table :medical
|
39
|
+
set_id_fld :id
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:medical_model_class) do
|
44
|
+
Class.new Pod4::Model do
|
45
|
+
include Pod4::Encrypting
|
46
|
+
|
47
|
+
encrypted_columns :name, :ailment
|
48
|
+
set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
|
49
|
+
set_iv_column :nonce
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#####
|
54
|
+
|
55
|
+
|
56
|
+
it "writes encrypted data to the database" do
|
57
|
+
m = medical_model_class.new
|
58
|
+
m.name = "frank"
|
59
|
+
m.ailment = "sore toe"
|
60
|
+
|
61
|
+
expect{ m.create }.not_to raise_exception
|
62
|
+
|
63
|
+
record = m.class.interface.list.first
|
64
|
+
expect( record ).not_to be_nil
|
65
|
+
end
|
66
|
+
|
67
|
+
it "reads encrypted data back from the database" do
|
68
|
+
m1 = medical_model_class.new
|
69
|
+
m1.name = "roger"
|
70
|
+
m1.ailment = "hiccups"
|
71
|
+
m1.create
|
72
|
+
|
73
|
+
m2 = medical_model_class.list.first
|
74
|
+
expect( m2 ).not_to be_nil
|
75
|
+
expect( m2.name ).to eq "roger"
|
76
|
+
expect( m2.ailment ).to eq "hiccups"
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "pod4"
|
2
|
+
require "pod4/pg_interface"
|
3
|
+
require "pod4/encrypting"
|
4
|
+
require "pg"
|
5
|
+
|
6
|
+
require_relative '../fixtures/database'
|
7
|
+
|
8
|
+
|
9
|
+
describe "(writing encrypted data via pg_interface)" do
|
10
|
+
|
11
|
+
def db_setup(connect)
|
12
|
+
client = PG.connect(connect)
|
13
|
+
client.exec(%Q| drop table if exists medical;
|
14
|
+
create table medical (
|
15
|
+
id serial primary key,
|
16
|
+
nonce text,
|
17
|
+
name text,
|
18
|
+
ailment text );| )
|
19
|
+
|
20
|
+
ensure
|
21
|
+
client.finish if client
|
22
|
+
end
|
23
|
+
|
24
|
+
def db_truncate(connect)
|
25
|
+
client = PG.connect(connect)
|
26
|
+
client.exec(%Q| truncate table medical restart identity; |)
|
27
|
+
ensure
|
28
|
+
client.finish if client
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
before(:all) do
|
33
|
+
@connect_hash = DB[:pg]
|
34
|
+
db_setup(@connect_hash)
|
35
|
+
end
|
36
|
+
|
37
|
+
before(:each) do
|
38
|
+
db_truncate(@connect_hash)
|
39
|
+
medical_model_class.set_interface medical_interface_class.new(@connect_hash)
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
let(:medical_interface_class) do
|
44
|
+
Class.new Pod4::PgInterface do
|
45
|
+
set_table :medical
|
46
|
+
set_id_fld :id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
let(:medical_model_class) do
|
51
|
+
Class.new Pod4::Model do
|
52
|
+
include Pod4::Encrypting
|
53
|
+
|
54
|
+
encrypted_columns :name, :ailment
|
55
|
+
set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
|
56
|
+
set_iv_column :nonce
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
#####
|
61
|
+
|
62
|
+
|
63
|
+
it "writes encrypted data to the database" do
|
64
|
+
m = medical_model_class.new
|
65
|
+
m.name = "frank"
|
66
|
+
m.ailment = "sore toe"
|
67
|
+
|
68
|
+
expect{ m.create }.not_to raise_exception
|
69
|
+
|
70
|
+
record = m.class.interface.list.first
|
71
|
+
expect( record ).not_to be_nil
|
72
|
+
end
|
73
|
+
|
74
|
+
it "reads encrypted data back from the database" do
|
75
|
+
m1 = medical_model_class.new
|
76
|
+
m1.name = "roger"
|
77
|
+
m1.ailment = "hiccups"
|
78
|
+
m1.create
|
79
|
+
|
80
|
+
m2 = medical_model_class.list.first
|
81
|
+
expect( m2 ).not_to be_nil
|
82
|
+
expect( m2.name ).to eq "roger"
|
83
|
+
expect( m2.ailment ).to eq "hiccups"
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
@@ -0,0 +1,611 @@
|
|
1
|
+
require 'pod4/pg_interface'
|
2
|
+
require 'pg'
|
3
|
+
|
4
|
+
require_relative '../common/shared_examples_for_interface'
|
5
|
+
require_relative '../fixtures/database'
|
6
|
+
|
7
|
+
|
8
|
+
describe "PgInterface" do
|
9
|
+
|
10
|
+
let(:pg_interface_class) do
|
11
|
+
Class.new PgInterface do
|
12
|
+
set_table :customer
|
13
|
+
set_id_fld :id
|
14
|
+
|
15
|
+
def stop; close; end # We open a lot of connections, unusually
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
let(:schema_interface_class) do
|
20
|
+
Class.new PgInterface do
|
21
|
+
set_schema :public
|
22
|
+
set_table :customer
|
23
|
+
set_id_fld :id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:prod_interface_class) do
|
28
|
+
Class.new PgInterface do
|
29
|
+
set_table :product
|
30
|
+
set_id_fld :code
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
let(:bad_interface_class1) do
|
35
|
+
Class.new PgInterface do
|
36
|
+
set_table :customer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
let(:bad_interface_class2) do
|
41
|
+
Class.new PgInterface do
|
42
|
+
set_id_fld :id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def db_setup(connect)
|
48
|
+
client = PG.connect(connect)
|
49
|
+
|
50
|
+
client.exec(%Q|
|
51
|
+
drop table if exists customer;
|
52
|
+
drop table if exists product;
|
53
|
+
|
54
|
+
create table customer (
|
55
|
+
id serial primary key,
|
56
|
+
medical_model_class.name text,
|
57
|
+
level real null,
|
58
|
+
day date null,
|
59
|
+
timestamp timestamp null,
|
60
|
+
price money null,
|
61
|
+
flag boolean null,
|
62
|
+
qty numeric null );
|
63
|
+
|
64
|
+
create table product (
|
65
|
+
code text,
|
66
|
+
name text );| )
|
67
|
+
|
68
|
+
ensure
|
69
|
+
client.finish if client
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def fill_data(ifce)
|
74
|
+
@data.each{|r| ifce.create(r) }
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def fill_product_data(ifce)
|
79
|
+
ifce.create( {code: "foo", name: "bar"} )
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
before(:all) do
|
84
|
+
@connect_hash = DB[:pg]
|
85
|
+
db_setup(@connect_hash)
|
86
|
+
|
87
|
+
@data = []
|
88
|
+
@data << { name: 'Barney',
|
89
|
+
level: 1.23,
|
90
|
+
day: Date.parse("2016-01-01"),
|
91
|
+
timestamp: Time.parse('2015-01-01 12:11'),
|
92
|
+
price: BigDecimal.new("1.24"),
|
93
|
+
flag: true,
|
94
|
+
qty: BigDecimal.new("1.25") }
|
95
|
+
|
96
|
+
@data << { name: 'Fred',
|
97
|
+
level: 2.34,
|
98
|
+
day: Date.parse("2016-02-02"),
|
99
|
+
timestamp: Time.parse('2015-01-02 12:22'),
|
100
|
+
price: BigDecimal.new("2.35"),
|
101
|
+
flag: false,
|
102
|
+
qty: BigDecimal.new("2.36") }
|
103
|
+
|
104
|
+
@data << { name: 'Betty',
|
105
|
+
level: 3.45,
|
106
|
+
day: Date.parse("2016-03-03"),
|
107
|
+
timestamp: Time.parse('2015-01-03 12:33'),
|
108
|
+
price: BigDecimal.new("3.46"),
|
109
|
+
flag: nil,
|
110
|
+
qty: BigDecimal.new("3.47") }
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
|
115
|
+
before do
|
116
|
+
interface.execute(%Q|
|
117
|
+
truncate table customer restart identity;
|
118
|
+
truncate table product;|)
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
after do
|
124
|
+
# We open a lot of connections, unusually
|
125
|
+
interface.stop if interface
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
let(:interface) do
|
130
|
+
pg_interface_class.new(@connect_hash)
|
131
|
+
end
|
132
|
+
|
133
|
+
let(:prod_interface) do
|
134
|
+
prod_interface_class.new(@connect_hash)
|
135
|
+
end
|
136
|
+
|
137
|
+
#####
|
138
|
+
|
139
|
+
|
140
|
+
it_behaves_like 'an interface' do
|
141
|
+
|
142
|
+
let(:interface) do
|
143
|
+
pg_interface_class.new(@connect_hash)
|
144
|
+
end
|
145
|
+
|
146
|
+
let(:record) { {name: 'Barney'} }
|
147
|
+
|
148
|
+
end
|
149
|
+
##
|
150
|
+
|
151
|
+
|
152
|
+
describe 'PgInterface.set_schema' do
|
153
|
+
it 'takes one argument' do
|
154
|
+
expect( pg_interface_class ).to respond_to(:set_schema).with(1).argument
|
155
|
+
end
|
156
|
+
end
|
157
|
+
##
|
158
|
+
|
159
|
+
|
160
|
+
describe 'PgInterface.schema' do
|
161
|
+
it 'returns the schema' do
|
162
|
+
expect( schema_interface_class.schema ).to eq :public
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'is optional' do
|
166
|
+
expect{ pg_interface_class.schema }.not_to raise_exception
|
167
|
+
expect( pg_interface_class.schema ).to eq nil
|
168
|
+
end
|
169
|
+
end
|
170
|
+
##
|
171
|
+
|
172
|
+
|
173
|
+
describe 'PgInterface.set_table' do
|
174
|
+
it 'takes one argument' do
|
175
|
+
expect( pg_interface_class ).to respond_to(:set_table).with(1).argument
|
176
|
+
end
|
177
|
+
end
|
178
|
+
##
|
179
|
+
|
180
|
+
|
181
|
+
describe 'PgInterface.table' do
|
182
|
+
it 'returns the table' do
|
183
|
+
expect( pg_interface_class.table ).to eq :customer
|
184
|
+
end
|
185
|
+
end
|
186
|
+
##
|
187
|
+
|
188
|
+
|
189
|
+
describe 'PgInterface.set_id_fld' do
|
190
|
+
it 'takes one argument' do
|
191
|
+
expect( pg_interface_class ).to respond_to(:set_id_fld).with(1).argument
|
192
|
+
end
|
193
|
+
end
|
194
|
+
##
|
195
|
+
|
196
|
+
|
197
|
+
describe 'PgInterface.id_fld' do
|
198
|
+
it 'returns the ID field name' do
|
199
|
+
expect( pg_interface_class.id_fld ).to eq :id
|
200
|
+
end
|
201
|
+
end
|
202
|
+
##
|
203
|
+
|
204
|
+
|
205
|
+
describe '#new' do
|
206
|
+
|
207
|
+
it 'requires a TinyTds connection string' do
|
208
|
+
expect{ pg_interface_class.new }.to raise_exception ArgumentError
|
209
|
+
expect{ pg_interface_class.new(nil) }.to raise_exception ArgumentError
|
210
|
+
expect{ pg_interface_class.new('foo') }.to raise_exception ArgumentError
|
211
|
+
|
212
|
+
expect{ pg_interface_class.new(@connect_hash) }.not_to raise_exception
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
##
|
217
|
+
|
218
|
+
|
219
|
+
describe '#quoted_table' do
|
220
|
+
|
221
|
+
it 'returns just the table when the schema is not set' do
|
222
|
+
expect( interface.quoted_table ).to eq( %Q|"customer"| )
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'returns the schema plus table when the schema is set' do
|
226
|
+
ifce = schema_interface_class.new(@connect_hash)
|
227
|
+
expect( ifce.quoted_table ).to eq( %|"public"."customer"| )
|
228
|
+
end
|
229
|
+
|
230
|
+
end
|
231
|
+
##
|
232
|
+
|
233
|
+
|
234
|
+
describe '#create' do
|
235
|
+
|
236
|
+
let(:hash) { {name: 'Bam-Bam', price: 4.44} }
|
237
|
+
let(:ot) { Octothorpe.new(name: 'Wilma', price: 5.55) }
|
238
|
+
|
239
|
+
it 'raises a Pod4::DatabaseError if anything goes wrong' do
|
240
|
+
expect{ interface.create(one: 'two') }.to raise_exception DatabaseError
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'creates the record when given a hash' do
|
244
|
+
# kinda impossible to seperate these two tests
|
245
|
+
id = interface.create(hash)
|
246
|
+
|
247
|
+
expect{ interface.read(id) }.not_to raise_exception
|
248
|
+
expect( interface.read(id).to_h ).to include hash
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'creates the record when given an Octothorpe' do
|
252
|
+
id = interface.create(ot)
|
253
|
+
|
254
|
+
expect{ interface.read(id) }.not_to raise_exception
|
255
|
+
expect( interface.read(id).to_h ).to include ot.to_h
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'shouldn\'t have a problem with record values of nil' do
|
259
|
+
record = {name: 'Ranger', price: nil}
|
260
|
+
expect{ interface.create(record) }.not_to raise_exception
|
261
|
+
id = interface.create(record)
|
262
|
+
expect( interface.read(id).to_h ).to include(record)
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'shouldn\'t have a problem with strings containing special characters' do
|
266
|
+
record = {name: %Q|T'Challa""|, price: nil}
|
267
|
+
expect{ interface.create(record) }.not_to raise_exception
|
268
|
+
id = interface.create(record)
|
269
|
+
expect( interface.read(id).to_h ).to include(record)
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'shouldn\'t have a problem with non-integer keys' do
|
273
|
+
hash = {code: "foo", name: "bar"}
|
274
|
+
id = prod_interface.create( Octothorpe.new(hash) )
|
275
|
+
|
276
|
+
expect( id ).to eq "foo"
|
277
|
+
expect{ prod_interface.read("foo") }.not_to raise_exception
|
278
|
+
expect( prod_interface.read("foo").to_h ).to include hash
|
279
|
+
end
|
280
|
+
|
281
|
+
end
|
282
|
+
##
|
283
|
+
|
284
|
+
|
285
|
+
describe '#read' do
|
286
|
+
before { fill_data(interface) }
|
287
|
+
|
288
|
+
it 'returns the record for the id as an Octothorpe' do
|
289
|
+
rec = interface.read(2)
|
290
|
+
expect( rec ).to be_a_kind_of Octothorpe
|
291
|
+
expect( rec.>>.name ).to eq 'Fred'
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'raises a Pod4::CantContinue if the ID is bad' do
|
295
|
+
expect{ interface.read(:foo) }.to raise_exception CantContinue
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'returns an empty Octothorpe if no record matches the ID' do
|
299
|
+
expect{ interface.read(99) }.not_to raise_exception
|
300
|
+
expect( interface.read(99) ).to be_a_kind_of Octothorpe
|
301
|
+
expect( interface.read(99) ).to be_empty
|
302
|
+
end
|
303
|
+
|
304
|
+
it 'returns real fields as Float' do
|
305
|
+
level = interface.read(1).>>.level
|
306
|
+
|
307
|
+
expect( level ).to be_a_kind_of Float
|
308
|
+
expect( level ).to be_within(0.001).of( @data.first[:level] )
|
309
|
+
end
|
310
|
+
|
311
|
+
it 'returns date fields as Date' do
|
312
|
+
date = interface.read(1).>>.day
|
313
|
+
|
314
|
+
expect( date ).to be_a_kind_of Date
|
315
|
+
expect( date ).to eq @data.first[:day]
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'returns datetime fields as Time' do
|
319
|
+
timestamp = interface.read(1).>>.timestamp
|
320
|
+
|
321
|
+
expect( timestamp ).to be_a_kind_of Time
|
322
|
+
expect( timestamp ).to eq @data.first[:timestamp]
|
323
|
+
end
|
324
|
+
|
325
|
+
it 'returns numeric fields as BigDecimal' do
|
326
|
+
qty = interface.read(1).>>.qty
|
327
|
+
|
328
|
+
expect( qty ).to be_a_kind_of BigDecimal
|
329
|
+
expect( qty ).to eq @data.first[:qty]
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'returns money fields as BigDecimal' do
|
333
|
+
price = interface.read(1).>>.price
|
334
|
+
|
335
|
+
expect( price ).to be_a_kind_of BigDecimal
|
336
|
+
expect( price ).to eq @data.first[:price]
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'returns boolean fields as boolean' do
|
340
|
+
[1,2,3].each do |i|
|
341
|
+
flag = interface.read(i).>>.flag
|
342
|
+
expect( [true, false, nil].include? flag ).to be true
|
343
|
+
expect( flag ).to be @data[i - 1][:flag]
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
it 'shouldn\'t have a problem with non-integer keys' do
|
348
|
+
# this is a 100% overlap with the create test above...
|
349
|
+
fill_product_data(prod_interface)
|
350
|
+
|
351
|
+
expect{ prod_interface.read("foo") }.not_to raise_exception
|
352
|
+
expect( prod_interface.read("foo").to_h ).to include(code: "foo", name: "bar")
|
353
|
+
end
|
354
|
+
|
355
|
+
end
|
356
|
+
##
|
357
|
+
|
358
|
+
|
359
|
+
describe '#list' do
|
360
|
+
before { fill_data(interface) }
|
361
|
+
|
362
|
+
it 'has an optional selection parameter, a hash' do
|
363
|
+
# Actually it does not have to be a hash, but FTTB we only support that.
|
364
|
+
expect{ interface.list(name: 'Barney') }.not_to raise_exception
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'returns an array of Octothorpes that match the records' do
|
368
|
+
# convert each OT to a hash and remove the ID key
|
369
|
+
arr = interface.list.map {|ot| x = ot.to_h; x.delete(:id); x }
|
370
|
+
|
371
|
+
expect( arr ).to match_array @data
|
372
|
+
end
|
373
|
+
|
374
|
+
it 'returns a subset of records based on the selection parameter' do
|
375
|
+
expect( interface.list(name: 'Fred').size ).to eq 1
|
376
|
+
|
377
|
+
expect( interface.list(name: 'Betty').first.to_h ).
|
378
|
+
to include(name: 'Betty')
|
379
|
+
|
380
|
+
end
|
381
|
+
|
382
|
+
it 'returns an empty Array if nothing matches' do
|
383
|
+
expect( interface.list(name: 'Yogi') ).to eq([])
|
384
|
+
end
|
385
|
+
|
386
|
+
it 'raises ArgumentError if the selection criteria is nonsensical' do
|
387
|
+
expect{ interface.list('foo') }.to raise_exception ArgumentError
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
##
|
392
|
+
|
393
|
+
|
394
|
+
describe '#update' do
|
395
|
+
before { fill_data(interface) }
|
396
|
+
|
397
|
+
let(:id) { interface.list.first[:id] }
|
398
|
+
|
399
|
+
def float_price(row)
|
400
|
+
row[:price] = row[:price].to_f
|
401
|
+
row
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'updates the record at ID with record parameter' do
|
405
|
+
record = {name: 'Booboo', price: 99.99}
|
406
|
+
interface.update(id, record)
|
407
|
+
|
408
|
+
expect( float_price( interface.read(id).to_h ) ).to include(record)
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'raises a CantContinue if anything weird happens with the ID' do
|
412
|
+
expect{ interface.update(99, name: 'Booboo') }.
|
413
|
+
to raise_exception CantContinue
|
414
|
+
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'raises a DatabaseError if anything weird happens with the record' do
|
418
|
+
expect{ interface.update(id, smarts: 'more') }.
|
419
|
+
to raise_exception DatabaseError
|
420
|
+
|
421
|
+
end
|
422
|
+
|
423
|
+
it 'shouldn\'t have a problem with record values of nil' do
|
424
|
+
record = {name: 'Ranger', price: nil}
|
425
|
+
expect{ interface.update(id, record) }.not_to raise_exception
|
426
|
+
expect( interface.read(id).to_h ).to include(record)
|
427
|
+
end
|
428
|
+
|
429
|
+
it 'shouldn\'t have a problem with strings containing special characters' do
|
430
|
+
record = {name: %Q|T'Challa""|, price: nil}
|
431
|
+
expect{ interface.update(id, record) }.not_to raise_exception
|
432
|
+
expect( interface.read(id).to_h ).to include(record)
|
433
|
+
end
|
434
|
+
|
435
|
+
it 'shouldn\'t have a problem with non-integer keys' do
|
436
|
+
fill_product_data(prod_interface)
|
437
|
+
expect{ prod_interface.update("foo", name: "baz") }.not_to raise_error
|
438
|
+
expect( prod_interface.read("foo").to_h[:name] ).to eq "baz"
|
439
|
+
end
|
440
|
+
|
441
|
+
end
|
442
|
+
##
|
443
|
+
|
444
|
+
|
445
|
+
describe '#delete' do
|
446
|
+
|
447
|
+
def list_contains(ifce, id)
|
448
|
+
ifce.list.find {|x| x[ifce.id_fld] == id }
|
449
|
+
end
|
450
|
+
|
451
|
+
let(:id) { interface.list.first[:id] }
|
452
|
+
|
453
|
+
before { fill_data(interface) }
|
454
|
+
|
455
|
+
it 'raises CantContinue if anything hinky happens with the id' do
|
456
|
+
expect{ interface.delete(:foo) }.to raise_exception CantContinue
|
457
|
+
expect{ interface.delete(99) }.to raise_exception CantContinue
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'makes the record at ID go away' do
|
461
|
+
expect( list_contains(interface, id) ).to be_truthy
|
462
|
+
interface.delete(id)
|
463
|
+
expect( list_contains(interface, id) ).to be_falsy
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'shouldn\'t have a problem with non-integer keys' do
|
467
|
+
fill_product_data(prod_interface)
|
468
|
+
expect( list_contains(prod_interface, "foo") ).to be_truthy
|
469
|
+
prod_interface.delete("foo")
|
470
|
+
expect( list_contains(prod_interface, "foo") ).to be_falsy
|
471
|
+
end
|
472
|
+
|
473
|
+
end
|
474
|
+
##
|
475
|
+
|
476
|
+
|
477
|
+
describe '#execute' do
|
478
|
+
|
479
|
+
let(:sql) { 'delete from customer where cast(price as numeric) < 2.0;' }
|
480
|
+
|
481
|
+
before { fill_data(interface) }
|
482
|
+
|
483
|
+
it 'requires an SQL string' do
|
484
|
+
expect{ interface.execute }.to raise_exception ArgumentError
|
485
|
+
expect{ interface.execute(nil) }.to raise_exception ArgumentError
|
486
|
+
expect{ interface.execute(14) }.to raise_exception ArgumentError
|
487
|
+
end
|
488
|
+
|
489
|
+
it 'raises some sort of Pod4 error if it runs into problems' do
|
490
|
+
expect{ interface.execute('delete from not_a_table') }.
|
491
|
+
to raise_exception Pod4Error
|
492
|
+
|
493
|
+
end
|
494
|
+
|
495
|
+
it 'executes the string' do
|
496
|
+
expect{ interface.execute(sql) }.not_to raise_exception
|
497
|
+
expect( interface.list.size ).to eq(@data.size - 1)
|
498
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
|
499
|
+
end
|
500
|
+
|
501
|
+
end
|
502
|
+
##
|
503
|
+
|
504
|
+
|
505
|
+
describe '#executep' do
|
506
|
+
|
507
|
+
let(:sql) { 'delete from customer where cast(price as numeric) < %s and name = %s;' }
|
508
|
+
|
509
|
+
before { fill_data(interface) }
|
510
|
+
|
511
|
+
it 'requires an SQL string' do
|
512
|
+
expect{ interface.executep }.to raise_exception ArgumentError
|
513
|
+
expect{ interface.executep(nil) }.to raise_exception ArgumentError
|
514
|
+
expect{ interface.executep(14) }.to raise_exception ArgumentError
|
515
|
+
end
|
516
|
+
|
517
|
+
it 'raises some sort of Pod4 error if it runs into problems' do
|
518
|
+
expect{ interface.executep('delete from not_a_table where foo = %s', 12) }.
|
519
|
+
to raise_exception Pod4Error
|
520
|
+
|
521
|
+
end
|
522
|
+
|
523
|
+
it 'executes the string with the given parameters' do
|
524
|
+
expect{ interface.executep(sql, 12.0, 'Barney') }.not_to raise_exception
|
525
|
+
expect( interface.list.size ).to eq(@data.size - 1)
|
526
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
|
527
|
+
end
|
528
|
+
|
529
|
+
end
|
530
|
+
##
|
531
|
+
|
532
|
+
|
533
|
+
describe '#select' do
|
534
|
+
|
535
|
+
before { fill_data(interface) }
|
536
|
+
|
537
|
+
it 'requires an SQL string' do
|
538
|
+
expect{ interface.select }.to raise_exception ArgumentError
|
539
|
+
expect{ interface.select(nil) }.to raise_exception ArgumentError
|
540
|
+
expect{ interface.select(14) }.to raise_exception ArgumentError
|
541
|
+
end
|
542
|
+
|
543
|
+
it 'raises some sort of Pod4 error if it runs into problems' do
|
544
|
+
expect{ interface.select('select * from not_a_table') }.
|
545
|
+
to raise_exception Pod4Error
|
546
|
+
|
547
|
+
end
|
548
|
+
|
549
|
+
it 'returns the result of the sql' do
|
550
|
+
sql1 = 'select name from customer where cast(price as numeric) < 2.0;'
|
551
|
+
sql2 = 'select name from customer where cast(price as numeric) < 0.0;'
|
552
|
+
|
553
|
+
expect{ interface.select(sql1) }.not_to raise_exception
|
554
|
+
expect( interface.select(sql1) ).to eq( [{name: 'Barney'}] )
|
555
|
+
expect( interface.select(sql2) ).to eq( [] )
|
556
|
+
end
|
557
|
+
|
558
|
+
it 'works if you pass a non-select' do
|
559
|
+
# By which I mean: still executes the SQL; returns []
|
560
|
+
sql = 'delete from customer where cast(price as numeric) < 2.0;'
|
561
|
+
ret = interface.select(sql)
|
562
|
+
|
563
|
+
expect( interface.list.size ).to eq(@data.size - 1)
|
564
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
|
565
|
+
expect( ret ).to eq( [] )
|
566
|
+
end
|
567
|
+
|
568
|
+
end
|
569
|
+
##
|
570
|
+
|
571
|
+
|
572
|
+
describe '#selectp' do
|
573
|
+
|
574
|
+
before { fill_data(interface) }
|
575
|
+
|
576
|
+
it 'requires an SQL string' do
|
577
|
+
expect{ interface.selectp }.to raise_exception ArgumentError
|
578
|
+
expect{ interface.selectp(nil) }.to raise_exception ArgumentError
|
579
|
+
expect{ interface.selectp(14) }.to raise_exception ArgumentError
|
580
|
+
end
|
581
|
+
|
582
|
+
it 'raises some sort of Pod4 error if it runs into problems' do
|
583
|
+
expect{ interface.selectp('select * from not_a_table where thingy = %s', 12) }.
|
584
|
+
to raise_exception Pod4Error
|
585
|
+
|
586
|
+
end
|
587
|
+
|
588
|
+
it 'returns the result of the sql' do
|
589
|
+
sql = 'select name from customer where cast(price as numeric) < %s;'
|
590
|
+
|
591
|
+
expect{ interface.selectp(sql, 2.0) }.not_to raise_exception
|
592
|
+
expect( interface.selectp(sql, 2.0) ).to eq( [{name: 'Barney'}] )
|
593
|
+
expect( interface.selectp(sql, 0.0) ).to eq( [] )
|
594
|
+
end
|
595
|
+
|
596
|
+
it 'works if you pass a non-select' do
|
597
|
+
sql = 'delete from customer where cast(price as numeric) < %s;'
|
598
|
+
ret = interface.selectp(sql, 2.0)
|
599
|
+
|
600
|
+
expect( interface.list.size ).to eq(@data.size - 1)
|
601
|
+
expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
|
602
|
+
expect( ret ).to eq( [] )
|
603
|
+
end
|
604
|
+
|
605
|
+
|
606
|
+
end
|
607
|
+
##
|
608
|
+
|
609
|
+
|
610
|
+
end
|
611
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "pod4"
|
2
|
+
require "pod4/sequel_interface"
|
3
|
+
require "pod4/encrypting"
|
4
|
+
require "sequel"
|
5
|
+
|
6
|
+
require_relative '../fixtures/database'
|
7
|
+
|
8
|
+
|
9
|
+
describe "(writing encrypted data via sequel_interface)" do
|
10
|
+
|
11
|
+
def db_setup(connection)
|
12
|
+
connection.run(%Q| drop table if exists medical;
|
13
|
+
create table medical (
|
14
|
+
id serial primary key,
|
15
|
+
nonce text,
|
16
|
+
name text,
|
17
|
+
ailment text );| )
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def db_truncate(connection)
|
22
|
+
connection.run(%Q| truncate table medical restart identity; |)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
before(:all) do
|
27
|
+
@connection = Sequel.postgres("pod4_test", DB[:sequel])
|
28
|
+
db_setup(@connection)
|
29
|
+
end
|
30
|
+
|
31
|
+
before(:each) do
|
32
|
+
db_truncate(@connection)
|
33
|
+
medical_model_class.set_interface medical_interface_class.new(@connection)
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
let(:medical_interface_class) do
|
38
|
+
Class.new Pod4::SequelInterface do
|
39
|
+
set_table :medical
|
40
|
+
set_id_fld :id
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
let(:medical_model_class) do
|
45
|
+
Class.new Pod4::Model do
|
46
|
+
include Pod4::Encrypting
|
47
|
+
|
48
|
+
encrypted_columns :name, :ailment
|
49
|
+
set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
|
50
|
+
set_iv_column :nonce
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#####
|
55
|
+
|
56
|
+
|
57
|
+
it "writes encrypted data to the database" do
|
58
|
+
m = medical_model_class.new
|
59
|
+
m.name = "frank"
|
60
|
+
m.ailment = "sore toe"
|
61
|
+
|
62
|
+
expect{ m.create }.not_to raise_exception
|
63
|
+
|
64
|
+
record = m.class.interface.list.first
|
65
|
+
expect( record ).not_to be_nil
|
66
|
+
end
|
67
|
+
|
68
|
+
it "reads encrypted data back from the database" do
|
69
|
+
m1 = medical_model_class.new
|
70
|
+
m1.name = "roger"
|
71
|
+
m1.ailment = "hiccups"
|
72
|
+
m1.create
|
73
|
+
|
74
|
+
m2 = medical_model_class.list.first
|
75
|
+
expect( m2 ).not_to be_nil
|
76
|
+
expect( m2.name ).to eq "roger"
|
77
|
+
expect( m2.ailment ).to eq "hiccups"
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,98 @@
|
|
1
|
+
require "pod4"
|
2
|
+
require "pod4/tds_interface"
|
3
|
+
require "pod4/encrypting"
|
4
|
+
require "tiny_tds"
|
5
|
+
|
6
|
+
require_relative '../fixtures/database'
|
7
|
+
|
8
|
+
|
9
|
+
describe "(writing encrypted data via tds_interface)" do
|
10
|
+
|
11
|
+
def db_setup(connect)
|
12
|
+
client = TinyTds::Client.new(connect)
|
13
|
+
client.execute(%Q|use [pod4_test];|).do
|
14
|
+
|
15
|
+
# Our SQL Server does not support DROP TABLE IF EXISTS !
|
16
|
+
# This is apparently an SQL-agnostic way of doing it:
|
17
|
+
client.execute(%Q|
|
18
|
+
if exists (select * from INFORMATION_SCHEMA.TABLES
|
19
|
+
where TABLE_NAME = 'medical'
|
20
|
+
and TABLE_SCHEMA = 'dbo' )
|
21
|
+
drop table dbo.medical;
|
22
|
+
|
23
|
+
create table dbo.medical (
|
24
|
+
id int identity(1,1) not null,
|
25
|
+
nonce nvarchar(max),
|
26
|
+
name nvarchar(max),
|
27
|
+
ailment nvarchar(max) );| ).do
|
28
|
+
|
29
|
+
ensure
|
30
|
+
client.close if client
|
31
|
+
end
|
32
|
+
|
33
|
+
def db_truncate(connect)
|
34
|
+
client = TinyTds::Client.new(connect)
|
35
|
+
client.execute(%Q|use [pod4_test];|).do
|
36
|
+
client.execute(%Q| truncate table medical; |).do
|
37
|
+
ensure
|
38
|
+
client.close if client
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
before(:all) do
|
43
|
+
@connect_hash = DB[:tds]
|
44
|
+
db_setup(@connect_hash)
|
45
|
+
end
|
46
|
+
|
47
|
+
before(:each) do
|
48
|
+
db_truncate(@connect_hash)
|
49
|
+
medical_model_class.set_interface medical_interface_class.new(@connect_hash)
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
let(:medical_interface_class) do
|
54
|
+
Class.new Pod4::TdsInterface do
|
55
|
+
set_db :pod4_test
|
56
|
+
set_table :medical
|
57
|
+
set_id_fld :id
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
let(:medical_model_class) do
|
62
|
+
Class.new Pod4::Model do
|
63
|
+
include Pod4::Encrypting
|
64
|
+
|
65
|
+
encrypted_columns :name, :ailment
|
66
|
+
set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
|
67
|
+
set_iv_column :nonce
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
#####
|
72
|
+
|
73
|
+
|
74
|
+
it "writes encrypted data to the database" do
|
75
|
+
m = medical_model_class.new
|
76
|
+
m.name = "frank"
|
77
|
+
m.ailment = "sore toe"
|
78
|
+
|
79
|
+
expect{ m.create }.not_to raise_exception
|
80
|
+
|
81
|
+
record = m.class.interface.list.first
|
82
|
+
expect( record ).not_to be_nil
|
83
|
+
end
|
84
|
+
|
85
|
+
it "reads encrypted data back from the database" do
|
86
|
+
m1 = medical_model_class.new
|
87
|
+
m1.name = "roger"
|
88
|
+
m1.ailment = "hiccups"
|
89
|
+
m1.create
|
90
|
+
|
91
|
+
m2 = medical_model_class.list.first
|
92
|
+
expect( m2 ).not_to be_nil
|
93
|
+
expect( m2.name ).to eq "roger"
|
94
|
+
expect( m2.ailment ).to eq "hiccups"
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pod4
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.10.
|
4
|
+
version: 0.10.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andy Jones
|
@@ -96,10 +96,15 @@ files:
|
|
96
96
|
- spec/common/sql_helper_spec.rb
|
97
97
|
- spec/doc_no_pending.rb
|
98
98
|
- spec/fixtures/database.rb
|
99
|
+
- spec/jruby/sequel_encrypting_jdbc_pg_spec.rb
|
99
100
|
- spec/jruby/sequel_interface_jdbc_ms_spec.rb
|
100
101
|
- spec/jruby/sequel_interface_jdbc_pg_spec.rb
|
102
|
+
- spec/mri/pg_encrypting_spec.rb
|
101
103
|
- spec/mri/pg_interface_spec.rb
|
104
|
+
- spec/mri/pg_interface_spec.rb.orig
|
105
|
+
- spec/mri/sequel_encrypting_spec.rb
|
102
106
|
- spec/mri/sequel_interface_spec.rb
|
107
|
+
- spec/mri/tds_encrypting_spec.rb
|
103
108
|
- spec/mri/tds_interface_spec.rb
|
104
109
|
- spec/spec_helper.rb
|
105
110
|
- tags
|
@@ -145,9 +150,14 @@ test_files:
|
|
145
150
|
- spec/common/sql_helper_spec.rb
|
146
151
|
- spec/doc_no_pending.rb
|
147
152
|
- spec/fixtures/database.rb
|
153
|
+
- spec/jruby/sequel_encrypting_jdbc_pg_spec.rb
|
148
154
|
- spec/jruby/sequel_interface_jdbc_ms_spec.rb
|
149
155
|
- spec/jruby/sequel_interface_jdbc_pg_spec.rb
|
156
|
+
- spec/mri/pg_encrypting_spec.rb
|
150
157
|
- spec/mri/pg_interface_spec.rb
|
158
|
+
- spec/mri/pg_interface_spec.rb.orig
|
159
|
+
- spec/mri/sequel_encrypting_spec.rb
|
151
160
|
- spec/mri/sequel_interface_spec.rb
|
161
|
+
- spec/mri/tds_encrypting_spec.rb
|
152
162
|
- spec/mri/tds_interface_spec.rb
|
153
163
|
- spec/spec_helper.rb
|