attr_secure 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|