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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a9f970443168e1a32a5bdb022ef5e74acedc5803
4
- data.tar.gz: a247cfc9a807256e6f8003346d28c08ffb4c4b5a
3
+ metadata.gz: 3e6810f8e6b2500a0fd71a8d2cd2755bbab0759f
4
+ data.tar.gz: b9d9017ec05194584f60af544a3564ba1f26cfc1
5
5
  SHA512:
6
- metadata.gz: 7b204d25fcbab4c785c7993d8fb65dade0369d73b10c27cbfbf39020a97020ac3809800148b2039bd586d268c9bac113ee26c755648763728dad23a6279eb438
7
- data.tar.gz: e9c3c0dbb2a835e38ea9e970e0d561ecb41964f8d418522de45469c38e51193c81b3e464b42c9c495fb29d2feb96df23cb76cd6cc693cce81cc46259d74e989f
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 create a key:
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
- and add it to your environment as `ATTR_SECURE_SECRET`.
46
- Then mark an attribute as secure:
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
- attr_secure :my_attribute
48
+ Alternatively, if you want to use different keys for different attributes you can do this too:
49
49
 
50
- and read and write as normal (see above example)
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 'fernet'
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
- def attr_secure(attribute, encryption_class = Secure.new)
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
- encrypted_value = encryption_class.encrypt(value.nil? ? nil : value)
26
- self.class.attr_secure_adapter.write_attribute self, attribute, encrypted_value
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
- encrypted_value = self.class.attr_secure_adapter.read_attribute(self, attribute)
31
- encryption_class.decrypt encrypted_value
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 ActiveRecord
@@ -1,5 +1,3 @@
1
- require 'attr_secure/secure'
2
-
3
1
  module AttrSecure
4
2
  module Adapters
5
3
  module Ruby
@@ -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
@@ -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 :env
9
+ attr_reader :secret
4
10
 
5
- def initialize(env=ENV)
6
- @env = env
11
+ def initialize(secret)
12
+ @secret = secret
7
13
  end
8
14
 
9
15
  def encrypt(value)
10
- Fernet.generate(attr_secure_secret) do |generator|
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(attr_secure_secret, value)
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
@@ -1,3 +1,3 @@
1
1
  module AttrSecure
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,30 +1,34 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe AttrSecure::Adapters::ActiveRecord do
4
- let(:described) { Class.new(ActiveRecord::Base) }
5
- subject { described.new }
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
- it 'has active record as it\'s adapter' do
15
- expect(described.attr_secure_adapter).to eq(AttrSecure::Adapters::ActiveRecord)
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
- it 'encrypts' do
19
- secure_mock.should_receive(:encrypt).with('hello').and_return('encrypted')
20
- subject.title = 'hello'
21
- expect(subject.attributes['title']).to eq('encrypted')
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
- it 'decrypts' do
25
- secure_mock.should_receive(:encrypt).with('hello').and_return('encrypted')
26
- subject.title = 'hello'
27
- secure_mock.should_receive(:decrypt).with('encrypted').and_return('decrypted')
28
- expect(subject.title).to eq('decrypted')
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
@@ -1,29 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe AttrSecure::Adapters::Ruby do
4
- let(:described) { Class.new }
5
- subject { described.new }
6
- let(:secure_mock) { double(AttrSecure::Secure) }
4
+ subject { Class.new }
7
5
 
8
- before do
9
- described.extend(AttrSecure)
10
- described.attr_secure :foo, secure_mock
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
- it 'has ruby as it\'s adapter' do
14
- expect(described.attr_secure_adapter).to eq(AttrSecure::Adapters::Ruby)
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
- it 'encrypts' do
18
- secure_mock.should_receive(:encrypt).with('hello').and_return('encrypted')
19
- subject.foo = 'hello'
20
- expect(subject.instance_variable_get(:@foo)).to eq('encrypted')
21
- end
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) { Class.new(Sequel::Model) }
5
- subject { described.new }
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
- it 'has sequel as it\'s adapter' do
15
- expect(described.attr_secure_adapter).to eq(AttrSecure::Adapters::Sequel)
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
- it 'encrypts' do
19
- secure_mock.should_receive(:encrypt).with('hello').and_return('encrypted')
20
- subject.title = 'hello'
21
- expect(subject.values[:title]).to eq('encrypted')
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
- it 'decrypts' do
25
- secure_mock.should_receive(:encrypt).with('hello').and_return('encrypted')
26
- subject.title = 'hello'
27
- secure_mock.should_receive(:decrypt).with('encrypted').and_return('decrypted')
28
- expect(subject.title).to eq('decrypted')
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
@@ -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 { described_class.new({'ATTR_SECURE_SECRET' => token}) }
5
- let(:token) { 'fWSvpC6Eh1/FFE1TUgXpcEzMmmGc9IZSqoexzEslzKI=' }
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.1.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-04 00:00:00.000000000 Z
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