attr_secure 0.1.0 → 0.2.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 +11 -5
- data/lib/attr_secure.rb +26 -11
- data/lib/attr_secure/adapters/active_record.rb +0 -2
- data/lib/attr_secure/adapters/ruby.rb +0 -2
- data/lib/attr_secure/adapters/sequel.rb +2 -4
- data/lib/attr_secure/secret.rb +23 -0
- data/lib/attr_secure/secure.rb +11 -14
- data/lib/attr_secure/version.rb +1 -1
- data/spec/adapters/active_record_spec.rb +20 -16
- data/spec/adapters/ruby_spec.rb +15 -19
- data/spec/adapters/sequel_spec.rb +20 -16
- data/spec/attr_secure_spec.rb +77 -0
- data/spec/secret_spec.rb +31 -0
- data/spec/secure_spec.rb +2 -2
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e6810f8e6b2500a0fd71a8d2cd2755bbab0759f
|
4
|
+
data.tar.gz: b9d9017ec05194584f60af544a3564ba1f26cfc1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df04a6247b167ff02122d97de4498cf2c89d2b3277799382985bab5e5a97f920a7b4847fb5ff789513da857a56252b5137145f548affed24fa7afcf1516d011f
|
7
|
+
data.tar.gz: 05418d708166a33ee050a171cdbe422373f54c1130e4f17334c12d5d93dc3fba9291a4052151136617e99457d6cefb5ebc4faddde5ec0b10c92ac0d483b1e49b
|
data/README.md
CHANGED
@@ -38,16 +38,22 @@ Or install it yourself as:
|
|
38
38
|
|
39
39
|
## Usage
|
40
40
|
|
41
|
-
To make an model attribute secure, first
|
41
|
+
To make an model attribute secure, first you need a secure key:
|
42
42
|
|
43
43
|
dd if=/dev/urandom bs=32 count=1 2>/dev/null | openssl base64
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
There's a number of ways of setting a key for a given attribute. The easiest is to default the key via the environment.
|
46
|
+
Setting the environment variable `ATTR_SECURE_SECRET` to a secret value will secure all attributes with the same key.
|
47
47
|
|
48
|
-
|
48
|
+
Alternatively, if you want to use different keys for different attributes you can do this too:
|
49
49
|
|
50
|
-
|
50
|
+
attr_secure :my_attribute, :secret => "EKq88AMFeRLqEx5knUcoJ4LOnrv52d7hfAFgEKMoDKzqNei4m7kbu"
|
51
|
+
|
52
|
+
If you would like your key dependent on something else, a lambda is OK too:
|
53
|
+
|
54
|
+
attr_secure :my_attribute, :secret => lambda {|record| record.user.secret }
|
55
|
+
|
56
|
+
Remember kids, it's not a good idea to hard-code secrets.
|
51
57
|
|
52
58
|
Note: You will want to set your table columns for encrypted values to :text or
|
53
59
|
similar. Encrypted values are long.
|
data/lib/attr_secure.rb
CHANGED
@@ -1,38 +1,53 @@
|
|
1
1
|
require "attr_secure/version"
|
2
|
-
require '
|
2
|
+
require 'attr_secure/secure'
|
3
|
+
require 'attr_secure/secret'
|
3
4
|
|
4
5
|
require 'attr_secure/adapters/ruby'
|
5
6
|
require 'attr_secure/adapters/active_record'
|
6
7
|
require 'attr_secure/adapters/sequel'
|
7
8
|
|
8
|
-
Fernet::Configuration.run do |config|
|
9
|
-
config.enforce_ttl = false
|
10
|
-
end
|
11
|
-
|
12
9
|
module AttrSecure
|
13
10
|
#
|
14
11
|
# All the available adapters.
|
15
12
|
# The order in this list matters, as only the first valid adapter will be used
|
16
13
|
#
|
17
14
|
ADAPTERS = [
|
18
|
-
AttrSecure::Adapters::Sequel,
|
19
15
|
AttrSecure::Adapters::ActiveRecord,
|
16
|
+
AttrSecure::Adapters::Sequel,
|
20
17
|
AttrSecure::Adapters::Ruby
|
21
18
|
]
|
22
19
|
|
23
|
-
|
20
|
+
# Generates attr_accessors that encrypt and decrypt attributes transparently
|
21
|
+
def attr_secure(*attributes)
|
22
|
+
options = {
|
23
|
+
:encryption_class => Secure,
|
24
|
+
:secret_class => Secret,
|
25
|
+
:env => ENV
|
26
|
+
}.merge!(attributes.last.is_a?(Hash) ? attributes.pop : {})
|
27
|
+
|
28
|
+
attribute = attributes.first
|
29
|
+
|
24
30
|
define_method("#{attribute}=") do |value|
|
25
|
-
|
26
|
-
|
31
|
+
adapter = self.class.attr_secure_adapter
|
32
|
+
secret = options[:secret_class].new(options).call(self)
|
33
|
+
crypter = options[:encryption_class].new(secret)
|
34
|
+
value = crypter.encrypt(value)
|
35
|
+
|
36
|
+
adapter.write_attribute self, attribute, value
|
27
37
|
end
|
28
38
|
|
29
39
|
define_method("#{attribute}") do
|
30
|
-
|
31
|
-
|
40
|
+
adapter = self.class.attr_secure_adapter
|
41
|
+
secret = options[:secret_class].new(options).call(self)
|
42
|
+
crypter = options[:encryption_class].new(secret)
|
43
|
+
value = adapter.read_attribute(self, attribute)
|
44
|
+
|
45
|
+
crypter.decrypt value
|
32
46
|
end
|
33
47
|
end
|
34
48
|
|
35
49
|
def attr_secure_adapter
|
36
50
|
ADAPTERS.find {|a| a.valid?(self) }
|
37
51
|
end
|
52
|
+
|
38
53
|
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'attr_secure/secure'
|
2
|
-
|
3
1
|
module AttrSecure
|
4
2
|
module Adapters
|
5
3
|
module Sequel
|
@@ -9,11 +7,11 @@ module AttrSecure
|
|
9
7
|
end
|
10
8
|
|
11
9
|
def self.write_attribute(object, attribute, value)
|
12
|
-
object[attribute] = value
|
10
|
+
object[attribute.to_sym] = value
|
13
11
|
end
|
14
12
|
|
15
13
|
def self.read_attribute(object, attribute)
|
16
|
-
object[attribute]
|
14
|
+
object[attribute.to_sym]
|
17
15
|
end
|
18
16
|
end
|
19
17
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module AttrSecure
|
2
|
+
class Secret
|
3
|
+
|
4
|
+
def initialize(options)
|
5
|
+
@secret, @env = options.values_at(:secret, :env)
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(object=nil)
|
9
|
+
if secret.respond_to?(:call)
|
10
|
+
secret.call(object)
|
11
|
+
else
|
12
|
+
secret || env!('ATTR_SECURE_SECRET')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
attr_reader :secret, :env
|
18
|
+
|
19
|
+
def env!(key)
|
20
|
+
env.fetch(key) { raise("Missing ENV(#{key})") }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/attr_secure/secure.rb
CHANGED
@@ -1,30 +1,27 @@
|
|
1
|
+
require 'fernet'
|
2
|
+
|
3
|
+
Fernet::Configuration.run do |config|
|
4
|
+
config.enforce_ttl = false
|
5
|
+
end
|
6
|
+
|
1
7
|
module AttrSecure
|
2
8
|
class Secure
|
3
|
-
attr_reader :
|
9
|
+
attr_reader :secret
|
4
10
|
|
5
|
-
def initialize(
|
6
|
-
@
|
11
|
+
def initialize(secret)
|
12
|
+
@secret = secret
|
7
13
|
end
|
8
14
|
|
9
15
|
def encrypt(value)
|
10
|
-
Fernet.generate(
|
16
|
+
Fernet.generate(secret) do |generator|
|
11
17
|
generator.data = { value: value }
|
12
18
|
end
|
13
19
|
end
|
14
20
|
|
15
21
|
def decrypt(value)
|
16
22
|
return nil if value.nil?
|
17
|
-
verifier = Fernet.verifier(
|
23
|
+
verifier = Fernet.verifier(secret, value)
|
18
24
|
verifier.data['value'] if verifier.valid?
|
19
25
|
end
|
20
|
-
|
21
|
-
private
|
22
|
-
def env!(key)
|
23
|
-
env.fetch(key) { raise("Missing ENV(#{key})") }
|
24
|
-
end
|
25
|
-
|
26
|
-
def attr_secure_secret
|
27
|
-
env!('ATTR_SECURE_SECRET')
|
28
|
-
end
|
29
26
|
end
|
30
27
|
end
|
data/lib/attr_secure/version.rb
CHANGED
@@ -1,30 +1,34 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AttrSecure::Adapters::ActiveRecord do
|
4
|
-
let(:described)
|
5
|
-
subject
|
6
|
-
let(:secure_mock) { double(AttrSecure::Secure) }
|
4
|
+
let(:described) { Class.new(ActiveRecord::Base) }
|
5
|
+
subject { described.new }
|
7
6
|
|
8
7
|
before do
|
9
8
|
described.table_name = 'fake_database'
|
10
|
-
described.extend(AttrSecure)
|
11
|
-
described.attr_secure :title, secure_mock
|
12
9
|
end
|
13
10
|
|
14
|
-
|
15
|
-
|
11
|
+
describe "valid?" do
|
12
|
+
it "should be valid" do
|
13
|
+
expect(described_class.valid?(described)).to be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not be valid" do
|
17
|
+
expect(described_class.valid?(String)).to be_false
|
18
|
+
end
|
16
19
|
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
describe "write attribute" do
|
22
|
+
it "should write an attribute" do
|
23
|
+
described_class.write_attribute(subject, 'title', 'hello')
|
24
|
+
expect(subject.title).to eq('hello')
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
describe "read attribute" do
|
29
|
+
it "should read an instance variable" do
|
30
|
+
subject.title = 'world'
|
31
|
+
expect(described_class.read_attribute(subject, 'title')).to eq('world')
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
data/spec/adapters/ruby_spec.rb
CHANGED
@@ -1,29 +1,25 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AttrSecure::Adapters::Ruby do
|
4
|
-
|
5
|
-
subject { described.new }
|
6
|
-
let(:secure_mock) { double(AttrSecure::Secure) }
|
4
|
+
subject { Class.new }
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
6
|
+
describe "valid?" do
|
7
|
+
it "should be valid all the time" do
|
8
|
+
expect(described_class.valid?(String)).to be_true
|
9
|
+
end
|
11
10
|
end
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
describe "write attribute" do
|
13
|
+
it "should set the instance variable" do
|
14
|
+
described_class.write_attribute(subject, 'secure', 'hello')
|
15
|
+
expect(subject.instance_variable_get("@secure")).to eql('hello')
|
16
|
+
end
|
15
17
|
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
it 'decrypts' do
|
24
|
-
secure_mock.should_receive(:encrypt).with('hello').and_return('encrypted')
|
25
|
-
subject.foo = 'hello'
|
26
|
-
secure_mock.should_receive(:decrypt).with('encrypted').and_return('decrypted')
|
27
|
-
expect(subject.foo).to eq('decrypted')
|
19
|
+
describe "read attribute" do
|
20
|
+
it "should read an instance variable" do
|
21
|
+
subject.instance_variable_set("@secure", 'world')
|
22
|
+
expect(described_class.read_attribute(subject, 'secure')).to eq('world')
|
23
|
+
end
|
28
24
|
end
|
29
25
|
end
|
@@ -1,30 +1,34 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AttrSecure::Adapters::Sequel do
|
4
|
-
let(:described)
|
5
|
-
subject
|
6
|
-
let(:secure_mock) { double(AttrSecure::Secure) }
|
4
|
+
let(:described) { Class.new(Sequel::Model) }
|
5
|
+
subject { described.new }
|
7
6
|
|
8
7
|
before do
|
9
8
|
described.set_dataset(:fake_database)
|
10
|
-
described.extend(AttrSecure)
|
11
|
-
described.attr_secure :title, secure_mock
|
12
9
|
end
|
13
10
|
|
14
|
-
|
15
|
-
|
11
|
+
describe "valid?" do
|
12
|
+
it "should be valid" do
|
13
|
+
expect(described_class.valid?(described)).to be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not be valid" do
|
17
|
+
expect(described_class.valid?(String)).to be_false
|
18
|
+
end
|
16
19
|
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
describe "write attribute" do
|
22
|
+
it "should write an attribute" do
|
23
|
+
described_class.write_attribute(subject, 'title', 'hello')
|
24
|
+
expect(subject.title).to eq('hello')
|
25
|
+
end
|
22
26
|
end
|
23
27
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
describe "read attribute" do
|
29
|
+
it "should read an instance variable" do
|
30
|
+
subject.title = 'world'
|
31
|
+
expect(described_class.read_attribute(subject, 'title')).to eq('world')
|
32
|
+
end
|
29
33
|
end
|
30
34
|
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AttrSecure do
|
4
|
+
describe "reading and writing attributes" do
|
5
|
+
subject { described.new }
|
6
|
+
let(:described) { Class.new }
|
7
|
+
let(:secure_mock) { double(AttrSecure::Secure) }
|
8
|
+
let(:secret_mock) { double(AttrSecure::Secret) }
|
9
|
+
let(:crypter) { mock(:secure_crypter) }
|
10
|
+
let(:adapter) { mock(:adapter) }
|
11
|
+
|
12
|
+
before do
|
13
|
+
described.extend(AttrSecure)
|
14
|
+
described.attr_secure :foo,
|
15
|
+
encryption_class: secure_mock,
|
16
|
+
secret_class: secret_mock
|
17
|
+
|
18
|
+
secret_mock.stub_chain(:new, :call).and_return('secret token')
|
19
|
+
secure_mock.should_receive(:new).with('secret token').and_return(crypter)
|
20
|
+
described.stub(:attr_secure_adapter).and_return(adapter)
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "set the attribute" do
|
24
|
+
it "should set the attribute encrypted" do
|
25
|
+
crypter.should_receive(:encrypt).with('decrypted').and_return('encrypted')
|
26
|
+
adapter.should_receive(:write_attribute).with(subject, :foo, 'encrypted')
|
27
|
+
subject.foo = 'decrypted'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "read the attribute" do
|
32
|
+
it "should read an encrypted attribute" do
|
33
|
+
adapter.should_receive(:read_attribute).with(subject, :foo).and_return('encrypted')
|
34
|
+
crypter.should_receive(:decrypt).with('encrypted').and_return('decrypted')
|
35
|
+
expect(subject.foo).to eq('decrypted')
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "retrieving the adapter" do
|
41
|
+
describe "ruby" do
|
42
|
+
subject { Class.new }
|
43
|
+
|
44
|
+
before do
|
45
|
+
subject.extend(AttrSecure)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should get the adapter" do
|
49
|
+
expect(subject.attr_secure_adapter).to eq(AttrSecure::Adapters::Ruby)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "active record" do
|
54
|
+
subject { Class.new(ActiveRecord::Base) }
|
55
|
+
|
56
|
+
before do
|
57
|
+
subject.extend(AttrSecure)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should get the adapter" do
|
61
|
+
expect(subject.attr_secure_adapter).to eq(AttrSecure::Adapters::ActiveRecord)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe "ruby" do
|
66
|
+
subject { Class.new(Sequel::Model) }
|
67
|
+
|
68
|
+
before do
|
69
|
+
subject.extend(AttrSecure)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should get the adapter" do
|
73
|
+
expect(subject.attr_secure_adapter).to eq(AttrSecure::Adapters::Sequel)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/spec/secret_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AttrSecure::Secret do
|
4
|
+
let(:env) { {'ATTR_SECURE_SECRET' => 'environment secret' } }
|
5
|
+
let(:options) { {secret: secret, env: env} }
|
6
|
+
subject { described_class.new(options) }
|
7
|
+
|
8
|
+
describe "with a nil secret" do
|
9
|
+
let(:secret) { nil }
|
10
|
+
|
11
|
+
it "should use the env variable" do
|
12
|
+
expect(subject.call).to eq('environment secret')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "with a string secret" do
|
17
|
+
let(:secret) { 'string secret' }
|
18
|
+
|
19
|
+
it "should use the string" do
|
20
|
+
expect(subject.call).to eq('string secret')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "with a lambda secret" do
|
25
|
+
let(:secret) { lambda {|o| o } }
|
26
|
+
|
27
|
+
it "should use the lambda" do
|
28
|
+
expect(subject.call('lambda secret')).to eq('lambda secret')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/spec/secure_spec.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AttrSecure::Secure do
|
4
|
-
subject
|
5
|
-
let(:
|
4
|
+
subject { described_class.new(secret) }
|
5
|
+
let(:secret) { 'fWSvpC6Eh1/FFE1TUgXpcEzMmmGc9IZSqoexzEslzKI=' }
|
6
6
|
|
7
7
|
describe 'encrypt' do
|
8
8
|
it "should encrypt a string" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attr_secure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Neil Middleton
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-06-
|
11
|
+
date: 2013-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec
|
@@ -112,11 +112,14 @@ files:
|
|
112
112
|
- lib/attr_secure/adapters/active_record.rb
|
113
113
|
- lib/attr_secure/adapters/ruby.rb
|
114
114
|
- lib/attr_secure/adapters/sequel.rb
|
115
|
+
- lib/attr_secure/secret.rb
|
115
116
|
- lib/attr_secure/secure.rb
|
116
117
|
- lib/attr_secure/version.rb
|
117
118
|
- spec/adapters/active_record_spec.rb
|
118
119
|
- spec/adapters/ruby_spec.rb
|
119
120
|
- spec/adapters/sequel_spec.rb
|
121
|
+
- spec/attr_secure_spec.rb
|
122
|
+
- spec/secret_spec.rb
|
120
123
|
- spec/secure_spec.rb
|
121
124
|
- spec/spec_helper.rb
|
122
125
|
- spec/support/load_active_record.rb
|
@@ -149,6 +152,8 @@ test_files:
|
|
149
152
|
- spec/adapters/active_record_spec.rb
|
150
153
|
- spec/adapters/ruby_spec.rb
|
151
154
|
- spec/adapters/sequel_spec.rb
|
155
|
+
- spec/attr_secure_spec.rb
|
156
|
+
- spec/secret_spec.rb
|
152
157
|
- spec/secure_spec.rb
|
153
158
|
- spec/spec_helper.rb
|
154
159
|
- spec/support/load_active_record.rb
|