encrypt_attributes 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.travis.yml +9 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +137 -0
- data/Rakefile +4 -0
- data/config/database.yml +4 -0
- data/config/database.yml.travis +4 -0
- data/config/mongoid.yml +6 -0
- data/db/migrate/20140610120934_create_users.rb +8 -0
- data/db/schema.rb +21 -0
- data/encrypt_attributes.gemspec +33 -0
- data/lib/encrypt_attributes.rb +29 -0
- data/lib/encrypt_attributes/adapters.rb +7 -0
- data/lib/encrypt_attributes/adapters/active_record.rb +10 -0
- data/lib/encrypt_attributes/adapters/base.rb +19 -0
- data/lib/encrypt_attributes/adapters/mongoid.rb +10 -0
- data/lib/encrypt_attributes/encryptor.rb +27 -0
- data/lib/encrypt_attributes/macros.rb +21 -0
- data/lib/encrypt_attributes/model.rb +12 -0
- data/lib/encrypt_attributes/version.rb +3 -0
- data/lib/tasks/active_record.rake +21 -0
- data/spec/adapters/active_record_spec.rb +14 -0
- data/spec/adapters/mongoid_spec.rb +18 -0
- data/spec/encryptor_spec.rb +91 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/support/connections/active_record.rb +10 -0
- data/spec/support/connections/mongoid.rb +6 -0
- data/spec/support/encryption_matcher.rb +23 -0
- data/spec/support/encryptor_helpers.rb +16 -0
- data/spec/support/examples/adapter.rb +81 -0
- data/spec/support/message_encryptor_helpers.rb +13 -0
- metadata +217 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 54d5d3aa82796fd065586ae0b609832b930a8c29
|
4
|
+
data.tar.gz: 865088b383ee116f2e16a0621209586f0ca856b2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2b0b1f47209ca40d8a36c94798f0de32b778c4f787433240920154282701824306ed4a7e9bd9b72d1a2766326edb231b428484bbeac0bceb327de81538392e70
|
7
|
+
data.tar.gz: 76d23dd15a2cf34783a90ea7a6e19f8c1773c4e3f9267c1c34aed82757025ca5783c1b3f6ff033b0ed428df05e364ed2b3402d44e69a38f3dc0ae65b559e847e
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
db/*.sqlite3
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Artur Hebda
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
# EncryptedAttributes
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/aenain/encrypted_attributes.svg?branch=master)](https://travis-ci.org/aenain/encrypted_attributes)
|
4
|
+
|
5
|
+
This gem provides a dead-simple encryption of string / text attributes of ActiveRecord and Mongoid models. Encryptor internally uses [ActiveSupport::MessageEncryptor](http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html) and therefore it uses 'aes-256-cbc' cipher by default. Currently there is no way to pass custom options to the encryptor. Gem does NOT require column with different name. It works just fine without it, but works only with columns of type `string` or `text`. From user perspective encryption is completely transparent - they use decrypted values all the time, but encrypted values are stored in database.
|
6
|
+
|
7
|
+
## ORM Support
|
8
|
+
|
9
|
+
Integration with ActiveRecord and Mongoid has been tested. Other frameworks should work as expected if they provide `#read_attribute` and `#write_attribute` methods. They are supposed to work in similar way as in ActiveRecord.
|
10
|
+
|
11
|
+
## Recommendations
|
12
|
+
|
13
|
+
Provided functionality is sufficient for storing oauth tokens and secrets that are not to be searched for. If you are looking for something more sophisticated, then check out my recommendations below.
|
14
|
+
|
15
|
+
[attr_encrypted](https://github.com/attr-encrypted/attr_encrypted) extends `Object`, so it works with any Ruby class, but requires an encrypted attribute to have a different name than the regular one.
|
16
|
+
|
17
|
+
[CryptKepper](http://jmazzi.github.io/crypt_keeper/) is a perfect solution if you need to search through encrypted fields, but supports ActiveRecord only.
|
18
|
+
|
19
|
+
[symmetric-encryption](https://github.com/reidmorrison/symmetric-encryption) is based on RSA-encrypted keys and supports ActiveRecord, MongoMapper and Mongoid. It provides type coercion and can be used to secure configuration files as well. PCI compliant.
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
Add this line to your application's Gemfile:
|
24
|
+
|
25
|
+
gem 'encrypt_attributes'
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install encrypt_attributes
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Provide a secret for `Encryptor`. Create a model with fields of type `string` or `text`, include `EncryptAttributes::Model` and call `encrypt_attrs(*attrs)`.
|
38
|
+
|
39
|
+
### Secret
|
40
|
+
|
41
|
+
If you are using Rails, secret by default will be set to `Rails.application.secrets.secret_key_base`. If you want to use a different secret, create an initializer:
|
42
|
+
|
43
|
+
# config/initializers/encrypted_attributes.rb
|
44
|
+
EncryptAttributes::Encryptor.secret = "<super-secure-key>"
|
45
|
+
|
46
|
+
If you do not use Rails, set the secret before any encryption is done, otherwise an error will be thrown.
|
47
|
+
|
48
|
+
### Models
|
49
|
+
|
50
|
+
* ActiveRecord
|
51
|
+
|
52
|
+
```
|
53
|
+
# db/migrate/create_authentications.rb
|
54
|
+
class CreateAuthentication < ActiveRecord::Migration
|
55
|
+
def change
|
56
|
+
create_table :authentications, force: true do |t|
|
57
|
+
t.string :token
|
58
|
+
t.string :secret
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
```
|
65
|
+
# app/models/authentication.rb
|
66
|
+
class Authentication < ActiveRecord::Base
|
67
|
+
include EncryptAttributes::Model
|
68
|
+
encrypt_attrs :token, :secret
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
* Mongoid
|
73
|
+
|
74
|
+
```
|
75
|
+
# app/models/authentication.rb
|
76
|
+
class Authentication
|
77
|
+
include Mongoid::Document
|
78
|
+
# it is important to include Mongoid::Document before
|
79
|
+
include EncryptAttributes::Model
|
80
|
+
|
81
|
+
field :token, type: String
|
82
|
+
field :secret, type: String
|
83
|
+
|
84
|
+
encrypt_attrs :token, :secret
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
### Custom accessors
|
89
|
+
|
90
|
+
If you want to define custom accessors for encrypted attributes, create methods you need to override and call `super` to execute encryption / decryption. It will work the same way with all supported ORMs.
|
91
|
+
|
92
|
+
# app/models/authentication.rb
|
93
|
+
class Authentication < ActiveRecord::Base
|
94
|
+
include EncryptAttributes::Model
|
95
|
+
encrypt_attrs :token, :secret
|
96
|
+
|
97
|
+
def secret
|
98
|
+
super.tap do |decrypted|
|
99
|
+
# do whatever you want here
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def secret=(new_secret)
|
104
|
+
super
|
105
|
+
# do whatever you want here
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
### Gotchas
|
110
|
+
|
111
|
+
Beware that each time encryption is done it returns a different value for the same input argument. Check out the consequences on the example below.
|
112
|
+
|
113
|
+
user = User.create(secret: "secret")
|
114
|
+
user.secret = "secret" # the same value
|
115
|
+
user.secret_changed? # returns true
|
116
|
+
|
117
|
+
ORMs will think that the secret has changed, because after setting a value, it gets encoded and a new value is stored in `attributes`.
|
118
|
+
|
119
|
+
## Running tests
|
120
|
+
|
121
|
+
Before running entire suite, you have to start Mongo DB, create and migrate database for ActiveRecord. Read more about [Mongo installation](http://docs.mongodb.org/manual/installation/).
|
122
|
+
To prepare database:
|
123
|
+
|
124
|
+
bundle exec rake db:create
|
125
|
+
bundle exec rake db:migrate RACK_ENV=test
|
126
|
+
|
127
|
+
To run tests:
|
128
|
+
|
129
|
+
bundle exec rspec
|
130
|
+
|
131
|
+
## Contributing
|
132
|
+
|
133
|
+
1. Fork it ( https://github.com/aenain/encrypted_attributes/fork )
|
134
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
135
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
136
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
137
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/config/database.yml
ADDED
data/config/mongoid.yml
ADDED
data/db/schema.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# This file is auto-generated from the current state of the database. Instead
|
3
|
+
# of editing this file, please use the migrations feature of Active Record to
|
4
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
5
|
+
#
|
6
|
+
# Note that this schema.rb definition is the authoritative source for your
|
7
|
+
# database schema. If you need to create the application database on another
|
8
|
+
# system, you should be using db:schema:load, not running all the migrations
|
9
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
|
+
#
|
12
|
+
# It's strongly recommended that you check this file into your version control system.
|
13
|
+
|
14
|
+
ActiveRecord::Schema.define(version: 20140610120934) do
|
15
|
+
|
16
|
+
create_table "users", force: true do |t|
|
17
|
+
t.string "token"
|
18
|
+
t.string "secret"
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
ENV["RACK_ENV"] ||= "test"
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'encrypt_attributes/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "encrypt_attributes"
|
9
|
+
spec.version = EncryptAttributes::VERSION
|
10
|
+
spec.authors = ["Artur Hebda"]
|
11
|
+
spec.email = ["arturhebda@gmail.com"]
|
12
|
+
spec.summary = "Dead-simple attributes encryption for ORMs"
|
13
|
+
spec.description = "This gem provides a dead-simple encryption of string / text attributes of ActiveRecord and Mongoid models. Encryptor internally uses ActiveSupport::MessageEncryptor and therefore it uses 'aes-256-cbc' cipher by default. Gem does NOT require column with different name. From user perspective encryption is completely transparent - they use decrypted values all the time, but encrypted values are stored in database."
|
14
|
+
spec.homepage = "https://github.com/aenain/encrypted_attributes"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_runtime_dependency "activesupport", ">= 4.1"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0.0.rc1"
|
27
|
+
spec.add_development_dependency "rspec-mocks", "~> 3.0.0.rc1"
|
28
|
+
|
29
|
+
spec.add_development_dependency "activerecord", ">= 4.1"
|
30
|
+
spec.add_development_dependency "mongoid", "~> 4.0.0.beta1"
|
31
|
+
spec.add_development_dependency "sqlite3"
|
32
|
+
spec.add_development_dependency "pry-byebug"
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "active_support/core_ext/object/try"
|
2
|
+
|
3
|
+
module EncryptAttributes
|
4
|
+
class MissingAdapterError < StandardError; end
|
5
|
+
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def find_adapter(base)
|
9
|
+
adapter = adapters.find do |receiver, adapter|
|
10
|
+
base.ancestors.map(&:to_s).include?(receiver)
|
11
|
+
end.try(:last)
|
12
|
+
|
13
|
+
adapter || (raise MissingAdapterError)
|
14
|
+
end
|
15
|
+
|
16
|
+
def register_adapter(receiver, adapter)
|
17
|
+
adapters[receiver] = adapter
|
18
|
+
end
|
19
|
+
|
20
|
+
def adapters
|
21
|
+
@adapters ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
require "encrypt_attributes/version"
|
26
|
+
require "encrypt_attributes/macros"
|
27
|
+
require "encrypt_attributes/encryptor"
|
28
|
+
require "encrypt_attributes/model"
|
29
|
+
require "encrypt_attributes/adapters"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module EncryptAttributes
|
2
|
+
module Adapters
|
3
|
+
module Base
|
4
|
+
def read_encrypted_attribute(attr_name)
|
5
|
+
encryptor.decrypt(read_attribute(attr_name))
|
6
|
+
end
|
7
|
+
|
8
|
+
def write_encrypted_attribute(attr_name, attr_value)
|
9
|
+
write_attribute(attr_name, encryptor.encrypt(attr_value))
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def encryptor
|
15
|
+
Encryptor.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "active_support/core_ext/module/attribute_accessors"
|
2
|
+
require "active_support/message_encryptor"
|
3
|
+
require "encrypt_attributes"
|
4
|
+
|
5
|
+
module EncryptAttributes
|
6
|
+
class Encryptor
|
7
|
+
cattr_accessor :secret
|
8
|
+
|
9
|
+
if defined?(Rails)
|
10
|
+
self.secret ||= Rails.application.secrets.secret_key_base
|
11
|
+
end
|
12
|
+
|
13
|
+
def encrypt(value)
|
14
|
+
return value if value.to_s.empty?
|
15
|
+
message_encryptor.encrypt_and_sign(value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def decrypt(value)
|
19
|
+
return value if value.to_s.empty?
|
20
|
+
message_encryptor.decrypt_and_verify(value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def message_encryptor
|
24
|
+
ActiveSupport::MessageEncryptor.new(self.class.secret)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module EncryptAttributes
|
2
|
+
module Macros
|
3
|
+
def encrypt_attrs(*attr_names)
|
4
|
+
mod = Module.new
|
5
|
+
mod.module_eval do
|
6
|
+
attr_names.each do |attr_name|
|
7
|
+
# getter
|
8
|
+
define_method(attr_name) do
|
9
|
+
read_encrypted_attribute(attr_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
# setter
|
13
|
+
define_method("#{attr_name}=") do |value|
|
14
|
+
write_encrypted_attribute(attr_name, value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
include mod
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Excerpt from https://github.com/janko-m/sinatra-activerecord/blob/master/lib/sinatra/activerecord/tasks.rake
|
2
|
+
require "active_record"
|
3
|
+
require "sqlite3"
|
4
|
+
|
5
|
+
load "active_record/railties/databases.rake"
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = YAML.load_file("config/database.yml")
|
8
|
+
|
9
|
+
ActiveRecord::Tasks::DatabaseTasks.tap do |config|
|
10
|
+
config.root = Rake.application.original_dir
|
11
|
+
config.env = "test"
|
12
|
+
config.db_dir = "db"
|
13
|
+
config.migrations_paths = ["db/migrate"]
|
14
|
+
config.database_configuration = ActiveRecord::Base.configurations
|
15
|
+
end
|
16
|
+
|
17
|
+
namespace :db do
|
18
|
+
task :environment do
|
19
|
+
ActiveRecord::Base.establish_connection(:test)
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/connections/active_record"
|
3
|
+
|
4
|
+
require "encrypt_attributes/model"
|
5
|
+
|
6
|
+
class User < ActiveRecord::Base
|
7
|
+
include EncryptAttributes::Model
|
8
|
+
encrypt_attrs :secret
|
9
|
+
end unless defined?(User)
|
10
|
+
|
11
|
+
describe User do
|
12
|
+
after(:all) { User.delete_all }
|
13
|
+
include_examples "adapter"
|
14
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "support/connections/mongoid"
|
3
|
+
|
4
|
+
require "encrypt_attributes/model"
|
5
|
+
|
6
|
+
class MongoidUser
|
7
|
+
include Mongoid::Document
|
8
|
+
include EncryptAttributes::Model
|
9
|
+
|
10
|
+
field :secret, type: String
|
11
|
+
|
12
|
+
encrypt_attrs :secret
|
13
|
+
end unless defined?(MongoidUser)
|
14
|
+
|
15
|
+
describe MongoidUser do
|
16
|
+
after(:all) { Mongoid.purge! }
|
17
|
+
include_examples "adapter"
|
18
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "encrypt_attributes/encryptor"
|
3
|
+
|
4
|
+
def set_valid_encryptor_secret
|
5
|
+
EncryptAttributes::Encryptor.secret = EncryptorHelpers::VALID_SECRET
|
6
|
+
end
|
7
|
+
|
8
|
+
describe EncryptAttributes::Encryptor do
|
9
|
+
include MessageEncryptorHelpers
|
10
|
+
|
11
|
+
it { should respond_to(:secret) }
|
12
|
+
it { should respond_to(:secret=) }
|
13
|
+
|
14
|
+
describe "#message_encryptor" do
|
15
|
+
let(:secret) { "secret" }
|
16
|
+
let(:encryptor_class) { ActiveSupport::MessageEncryptor }
|
17
|
+
|
18
|
+
it "uses secret to instantiate encryptor" do
|
19
|
+
described_class.secret = secret
|
20
|
+
allow(encryptor_class).to receive(:new)
|
21
|
+
described_class.new.message_encryptor
|
22
|
+
expect(encryptor_class).to have_received(:new).with(secret)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "#encrypt" do
|
27
|
+
subject { described_class.new }
|
28
|
+
|
29
|
+
before(:each) do
|
30
|
+
set_valid_encryptor_secret
|
31
|
+
end
|
32
|
+
|
33
|
+
context "nil" do
|
34
|
+
it "returns nil" do
|
35
|
+
expect(subject.encrypt(nil)).to be_nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context "empty string" do
|
40
|
+
it "returns empty string" do
|
41
|
+
expect(subject.encrypt("")).to eq ""
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "not empty value" do
|
46
|
+
let(:decrypted) { "decrypted" }
|
47
|
+
|
48
|
+
it "encrypts correctly" do
|
49
|
+
expect(subject.encrypt(decrypted))
|
50
|
+
.to be_encryption_of(decrypted)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#decrypt" do
|
56
|
+
subject { described_class.new }
|
57
|
+
|
58
|
+
before(:each) do
|
59
|
+
set_valid_encryptor_secret
|
60
|
+
end
|
61
|
+
|
62
|
+
context "nil" do
|
63
|
+
it "returns nil" do
|
64
|
+
expect(subject.decrypt(nil)).to be_nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "empty string" do
|
69
|
+
it "returns empty string" do
|
70
|
+
expect(subject.decrypt("")).to eq ""
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context "not empty value" do
|
75
|
+
let(:decrypted) { "decrypted" }
|
76
|
+
let(:encrypted) { subject.encrypt(decrypted) }
|
77
|
+
|
78
|
+
it "decrypts correctly" do
|
79
|
+
expect(subject.decrypt(encrypted)).to eq decrypted
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context "tampered value" do
|
84
|
+
it "raises error" do
|
85
|
+
expect {
|
86
|
+
subject.decrypt("tampered")
|
87
|
+
}.to raise_error(ActiveSupport::MessageVerifier::InvalidSignature)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
RSpec::Matchers.define :be_encryption_of do |expected|
|
2
|
+
match do |actual|
|
3
|
+
begin
|
4
|
+
secret = EncryptAttributes::Encryptor.secret
|
5
|
+
encryptor = ActiveSupport::MessageEncryptor.new(secret)
|
6
|
+
encryptor.decrypt_and_verify(actual) == expected
|
7
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
8
|
+
false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
failure_message do |actual|
|
13
|
+
"expected that #{actual} would be an encryption of #{expected}"
|
14
|
+
end
|
15
|
+
|
16
|
+
failure_message_when_negated do |actual|
|
17
|
+
"expected that #{actual} would not be an encryption of #{expected}"
|
18
|
+
end
|
19
|
+
|
20
|
+
description do
|
21
|
+
"be an encryption of #{expected}"
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module EncryptorHelpers
|
2
|
+
VALID_SECRET = "14a678aa4a78286d94878d2256f6e591b53707b3f1ec9cdd4cbfde1514670d32".freeze
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.class_eval do
|
6
|
+
let(:decrypted) { "decrypted" }
|
7
|
+
let(:encrypted) { encrypt(decrypted) }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def encrypt(value)
|
12
|
+
encryptor = EncryptAttributes::Encryptor.new
|
13
|
+
encryptor.class.secret ||= VALID_SECRET
|
14
|
+
encryptor.encrypt(value)
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
shared_examples_for "adapter:storing" do
|
2
|
+
subject { described_class.new }
|
3
|
+
|
4
|
+
describe "storing" do
|
5
|
+
it "encrypts attributes" do
|
6
|
+
subject.secret = decrypted
|
7
|
+
subject.save
|
8
|
+
|
9
|
+
expect(read_attribute.call(:secret)).to eq encrypted
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
shared_examples_for "adapter:retrieving" do
|
15
|
+
subject { described_class.create(secret: decrypted) }
|
16
|
+
|
17
|
+
describe "retrieving" do
|
18
|
+
it "decrypts specified attributes" do
|
19
|
+
subject.reload
|
20
|
+
expect(subject.secret).to eq decrypted
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
shared_examples_for "adapter:overrides:reader" do
|
26
|
+
subject { described_class.create(secret: decrypted) }
|
27
|
+
|
28
|
+
describe "overriden reader" do
|
29
|
+
it "supports super chain" do
|
30
|
+
def subject.secret
|
31
|
+
super.tap { |v| yield v if block_given? }
|
32
|
+
end
|
33
|
+
|
34
|
+
injection = double(call: nil)
|
35
|
+
subject.secret { |v| injection.call(v) }
|
36
|
+
|
37
|
+
expect(injection).to have_received(:call).with(decrypted)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
shared_examples_for "adapter:overrides:writer" do
|
43
|
+
subject { described_class.create(secret: decrypted) }
|
44
|
+
|
45
|
+
describe "overriden writer" do
|
46
|
+
let(:injection) { double(call: nil) }
|
47
|
+
|
48
|
+
before(:each) do
|
49
|
+
subject.instance_variable_set(:@injection, injection)
|
50
|
+
def subject.secret=(new_secret)
|
51
|
+
@injection.call
|
52
|
+
super
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "supports super chain" do
|
57
|
+
subject.secret = decrypted
|
58
|
+
subject.save
|
59
|
+
|
60
|
+
expect(injection).to have_received(:call)
|
61
|
+
expect(read_attribute.call(:secret)).to eq encrypted
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
shared_examples_for "adapter" do
|
67
|
+
include EncryptorHelpers
|
68
|
+
|
69
|
+
let(:read_attribute) { subject.public_method(:read_attribute) }
|
70
|
+
let(:encryptor) { double(encrypt: encrypted, decrypt: decrypted) }
|
71
|
+
|
72
|
+
before(:each) do
|
73
|
+
allow(EncryptAttributes::Encryptor)
|
74
|
+
.to receive(:new).and_return(encryptor)
|
75
|
+
end
|
76
|
+
|
77
|
+
include_examples "adapter:storing"
|
78
|
+
include_examples "adapter:retrieving"
|
79
|
+
include_examples "adapter:overrides:reader"
|
80
|
+
include_examples "adapter:overrides:writer"
|
81
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module MessageEncryptorHelpers
|
2
|
+
def stub_message_encryption(input, output)
|
3
|
+
allow_any_instance_of(ActiveSupport::MessageEncryptor)
|
4
|
+
.to receive(:encrypt_and_sign).with(input)
|
5
|
+
.and_return(output)
|
6
|
+
end
|
7
|
+
|
8
|
+
def stub_message_decryption(input, output)
|
9
|
+
allow_any_instance_of(ActiveSupport::MessageEncryptor)
|
10
|
+
.to receive(:decrypt_and_verify).with(input)
|
11
|
+
.and_return(output)
|
12
|
+
end
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: encrypt_attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Artur Hebda
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0.rc1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0.rc1
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-mocks
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.0.0.rc1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.0.0.rc1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activerecord
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '4.1'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '4.1'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: mongoid
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 4.0.0.beta1
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 4.0.0.beta1
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sqlite3
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry-byebug
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
description: This gem provides a dead-simple encryption of string / text attributes
|
140
|
+
of ActiveRecord and Mongoid models. Encryptor internally uses ActiveSupport::MessageEncryptor
|
141
|
+
and therefore it uses 'aes-256-cbc' cipher by default. Gem does NOT require column
|
142
|
+
with different name. From user perspective encryption is completely transparent
|
143
|
+
- they use decrypted values all the time, but encrypted values are stored in database.
|
144
|
+
email:
|
145
|
+
- arturhebda@gmail.com
|
146
|
+
executables: []
|
147
|
+
extensions: []
|
148
|
+
extra_rdoc_files: []
|
149
|
+
files:
|
150
|
+
- ".gitignore"
|
151
|
+
- ".travis.yml"
|
152
|
+
- Gemfile
|
153
|
+
- LICENSE.txt
|
154
|
+
- README.md
|
155
|
+
- Rakefile
|
156
|
+
- config/database.yml
|
157
|
+
- config/database.yml.travis
|
158
|
+
- config/mongoid.yml
|
159
|
+
- db/migrate/20140610120934_create_users.rb
|
160
|
+
- db/schema.rb
|
161
|
+
- encrypt_attributes.gemspec
|
162
|
+
- lib/encrypt_attributes.rb
|
163
|
+
- lib/encrypt_attributes/adapters.rb
|
164
|
+
- lib/encrypt_attributes/adapters/active_record.rb
|
165
|
+
- lib/encrypt_attributes/adapters/base.rb
|
166
|
+
- lib/encrypt_attributes/adapters/mongoid.rb
|
167
|
+
- lib/encrypt_attributes/encryptor.rb
|
168
|
+
- lib/encrypt_attributes/macros.rb
|
169
|
+
- lib/encrypt_attributes/model.rb
|
170
|
+
- lib/encrypt_attributes/version.rb
|
171
|
+
- lib/tasks/active_record.rake
|
172
|
+
- spec/adapters/active_record_spec.rb
|
173
|
+
- spec/adapters/mongoid_spec.rb
|
174
|
+
- spec/encryptor_spec.rb
|
175
|
+
- spec/spec_helper.rb
|
176
|
+
- spec/support/connections/active_record.rb
|
177
|
+
- spec/support/connections/mongoid.rb
|
178
|
+
- spec/support/encryption_matcher.rb
|
179
|
+
- spec/support/encryptor_helpers.rb
|
180
|
+
- spec/support/examples/adapter.rb
|
181
|
+
- spec/support/message_encryptor_helpers.rb
|
182
|
+
homepage: https://github.com/aenain/encrypted_attributes
|
183
|
+
licenses:
|
184
|
+
- MIT
|
185
|
+
metadata: {}
|
186
|
+
post_install_message:
|
187
|
+
rdoc_options: []
|
188
|
+
require_paths:
|
189
|
+
- lib
|
190
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
196
|
+
requirements:
|
197
|
+
- - ">="
|
198
|
+
- !ruby/object:Gem::Version
|
199
|
+
version: '0'
|
200
|
+
requirements: []
|
201
|
+
rubyforge_project:
|
202
|
+
rubygems_version: 2.2.2
|
203
|
+
signing_key:
|
204
|
+
specification_version: 4
|
205
|
+
summary: Dead-simple attributes encryption for ORMs
|
206
|
+
test_files:
|
207
|
+
- spec/adapters/active_record_spec.rb
|
208
|
+
- spec/adapters/mongoid_spec.rb
|
209
|
+
- spec/encryptor_spec.rb
|
210
|
+
- spec/spec_helper.rb
|
211
|
+
- spec/support/connections/active_record.rb
|
212
|
+
- spec/support/connections/mongoid.rb
|
213
|
+
- spec/support/encryption_matcher.rb
|
214
|
+
- spec/support/encryptor_helpers.rb
|
215
|
+
- spec/support/examples/adapter.rb
|
216
|
+
- spec/support/message_encryptor_helpers.rb
|
217
|
+
has_rdoc:
|