mongoid-encrypted-fields 1.0.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.
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/.rvmrc +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +50 -0
- data/Rakefile +1 -0
- data/lib/mongoid-encrypted-fields.rb +23 -0
- data/lib/mongoid-encrypted-fields/ciphers/asymmetric_cipher.rb +22 -0
- data/lib/mongoid-encrypted-fields/ciphers/cipher.rb +15 -0
- data/lib/mongoid-encrypted-fields/ciphers/symmetric_cipher.rb +22 -0
- data/lib/mongoid-encrypted-fields/fields/encrypted_date.rb +33 -0
- data/lib/mongoid-encrypted-fields/fields/encrypted_date_time.rb +33 -0
- data/lib/mongoid-encrypted-fields/fields/encrypted_field.rb +75 -0
- data/lib/mongoid-encrypted-fields/fields/encrypted_string.rb +30 -0
- data/lib/mongoid-encrypted-fields/fields/encrypted_time.rb +33 -0
- data/lib/mongoid-encrypted-fields/logging.rb +59 -0
- data/lib/mongoid-encrypted-fields/version.rb +5 -0
- data/mongoid-encrypted-fields.gemspec +25 -0
- data/spec/config/mongoid.yml +9 -0
- data/spec/mongoid-encrypted-fields/fields/encrypted_date_spec.rb +69 -0
- data/spec/mongoid-encrypted-fields/fields/encrypted_datetime_spec.rb +69 -0
- data/spec/mongoid-encrypted-fields/fields/encrypted_field_spec.rb +30 -0
- data/spec/mongoid-encrypted-fields/fields/encrypted_string_spec.rb +79 -0
- data/spec/mongoid-encrypted-fields/fields/encrypted_time_spec.rb +69 -0
- data/spec/mongoid-encrypted-fields/fields/model_spec.rb +74 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/models/person.rb +8 -0
- metadata +152 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use 1.9.3@mongoid-encrypted-fields --create
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Koan Health
|
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,50 @@
|
|
1
|
+
mongoid-encrypted-fields
|
2
|
+
========================
|
3
|
+
|
4
|
+
A library for storing encrypted data in Mongo using Mongoid. We looked at a few alternatives, but wanted something that stored the values securely and unobtrusively.
|
5
|
+
|
6
|
+
Mongoid 3 supports [custom types](http://mongoid.org/en/mongoid/docs/documents.html) that need to only provide a simple interface - allowing us to extend core Ruby types to secure any type while providing a clean interface for developers.
|
7
|
+
|
8
|
+
Queries encrypt data before searching the database, so equality matches work automatically.
|
9
|
+
|
10
|
+
## Prerequisites
|
11
|
+
* Ruby 1.9.3
|
12
|
+
* [Mongoid](http://mongoid.org) 3.0
|
13
|
+
* [Encrypted-Strings](https://github.com/pluginaweek/encrypted_strings)
|
14
|
+
|
15
|
+
## Install
|
16
|
+
gem 'mongoid-encrypted-fields'
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
* Configure the cipher to be used for encrypting field values:
|
20
|
+
```Ruby
|
21
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: ENV['MY_PASSWORD']) # find a secure way to get your password
|
22
|
+
```
|
23
|
+
* Use encrypted types for fields in your models:
|
24
|
+
```Ruby
|
25
|
+
class Person
|
26
|
+
include Mongoid::Document
|
27
|
+
|
28
|
+
field :name, type: String
|
29
|
+
field :ssn, type: Mongoid::EncryptedString
|
30
|
+
end
|
31
|
+
```
|
32
|
+
* The field getter returns the unencrypted value:
|
33
|
+
```Ruby
|
34
|
+
person = Person.new(ssn: '123456789')
|
35
|
+
person.ssn # => '123456789'
|
36
|
+
```
|
37
|
+
* The encrypted value is accessible with the "encrypted" attribute
|
38
|
+
```Ruby
|
39
|
+
person.ssn.encrypted # => <encrypted string>
|
40
|
+
```
|
41
|
+
* Finding a model by an encrypted field works automatically (equality only):
|
42
|
+
```Ruby
|
43
|
+
Person.where(ssn: '123456789').count() # ssn is encrypted before querying the database
|
44
|
+
```
|
45
|
+
|
46
|
+
## Known Limitations
|
47
|
+
* Single cipher for all encrypted fields
|
48
|
+
|
49
|
+
## Copyright
|
50
|
+
(c) 2012 Koan Health. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'encrypted_strings'
|
2
|
+
|
3
|
+
require 'mongoid-encrypted-fields/version'
|
4
|
+
require 'mongoid-encrypted-fields/logging'
|
5
|
+
require 'mongoid-encrypted-fields/ciphers/cipher'
|
6
|
+
require 'mongoid-encrypted-fields/ciphers/asymmetric_cipher'
|
7
|
+
require 'mongoid-encrypted-fields/ciphers/symmetric_cipher'
|
8
|
+
require 'mongoid-encrypted-fields/fields/encrypted_field'
|
9
|
+
require 'mongoid-encrypted-fields/fields/encrypted_string'
|
10
|
+
require 'mongoid-encrypted-fields/fields/encrypted_date'
|
11
|
+
require 'mongoid-encrypted-fields/fields/encrypted_date_time'
|
12
|
+
require 'mongoid-encrypted-fields/fields/encrypted_time'
|
13
|
+
|
14
|
+
module Mongoid
|
15
|
+
module EncryptedFields
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Set cipher used for all field encryption/decryption
|
19
|
+
attr_accessor :cipher
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ciphers
|
3
|
+
class AsymmetricCipher < Cipher
|
4
|
+
|
5
|
+
attr_reader :algorithm, :password, :public_key_file, :private_key_file
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@options = options
|
9
|
+
@options.each { |key, value| instance_variable_set "@#{key}", value }
|
10
|
+
end
|
11
|
+
|
12
|
+
def encrypt(data)
|
13
|
+
data.encrypt(:asymmetric, @options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrypt(data)
|
17
|
+
data.decrypt(:asymmetric, @options)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ciphers
|
3
|
+
class Cipher
|
4
|
+
|
5
|
+
def encrypt(data)
|
6
|
+
raise NotImplementedError.new('encrypt must be implemented')
|
7
|
+
end
|
8
|
+
|
9
|
+
def decrypt(data)
|
10
|
+
raise NotImplementedError.new('decrypt must be implemented')
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module Ciphers
|
3
|
+
class SymmetricCipher < Cipher
|
4
|
+
|
5
|
+
attr_reader :algorithm, :password
|
6
|
+
|
7
|
+
def initialize(options = { })
|
8
|
+
@options = options
|
9
|
+
@options.each { |key, value| instance_variable_set "@#{key}", value }
|
10
|
+
end
|
11
|
+
|
12
|
+
def encrypt(data)
|
13
|
+
data.encrypt(:symmetric, @options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrypt(data)
|
17
|
+
data.decrypt(:symmetric, @options)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Used to store an encrypted date in Mongo
|
3
|
+
#
|
4
|
+
# Usage:
|
5
|
+
# field :birth_date, type: Mongoid::EncryptedDate
|
6
|
+
#
|
7
|
+
# Set with an unencrypted date
|
8
|
+
# p = Person.new()
|
9
|
+
# p.birth_date = Date.new(2000, 1, 1)
|
10
|
+
#
|
11
|
+
# Get returns the unencrypted date
|
12
|
+
# puts p.birth_date -> 'Jan 1, 2000'
|
13
|
+
#
|
14
|
+
# Use the encrypted property to see the encrypted value
|
15
|
+
# puts p.birth_date.encrypted -> '....'
|
16
|
+
#
|
17
|
+
module Mongoid
|
18
|
+
class EncryptedDate < ::Date
|
19
|
+
include Mongoid::EncryptedField
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def from_date(date)
|
24
|
+
parse(date.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert(object)
|
28
|
+
from_date(object.to_datetime)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Used to store an encrypted datetime in Mongo
|
3
|
+
#
|
4
|
+
# Usage:
|
5
|
+
# field :birth_date, type: Mongoid::EncryptedDate
|
6
|
+
#
|
7
|
+
# Set with an unencrypted date
|
8
|
+
# p = Person.new()
|
9
|
+
# p.birth_date = Date.new(2000, 1, 1)
|
10
|
+
#
|
11
|
+
# Get returns the unencrypted date
|
12
|
+
# puts p.birth_date -> 'Jan 1, 2000'
|
13
|
+
#
|
14
|
+
# Use the encrypted property to see the encrypted value
|
15
|
+
# puts p.birth_date.encrypted -> '....'
|
16
|
+
#
|
17
|
+
module Mongoid
|
18
|
+
class EncryptedDateTime < ::DateTime
|
19
|
+
include Mongoid::EncryptedField
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def from_datetime(d)
|
24
|
+
parse(d.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert(object)
|
28
|
+
from_datetime(object.to_datetime)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "base64"
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
module EncryptedField
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def encrypted
|
8
|
+
if frozen?
|
9
|
+
@encrypted ||= self.class.encrypt(to_s)
|
10
|
+
else
|
11
|
+
# We are mutable - need to encrypt whenever asked
|
12
|
+
self.class.encrypt(to_s)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Converts an object of this instance into a database friendly value.
|
17
|
+
def mongoize
|
18
|
+
encrypted
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
# Get the object as it was stored in the database, and instantiate this custom class from it.
|
24
|
+
def demongoize(object)
|
25
|
+
#EncryptedFields.logger.debug "#{name}##{__method__.to_s}: #{object.inspect}"
|
26
|
+
case
|
27
|
+
when object.is_a?(self.class) || object.blank?
|
28
|
+
object
|
29
|
+
else
|
30
|
+
decrypted = is_encrypted?(object) ? decrypt(object) : object
|
31
|
+
convert(decrypted)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Takes any possible object and converts it to how it would be stored in the database.
|
36
|
+
def mongoize(object)
|
37
|
+
#EncryptedFields.logger.debug "#{name}##{__method__.to_s}: #{object.inspect}"
|
38
|
+
case
|
39
|
+
when object.is_a?(self.class)
|
40
|
+
object.mongoize
|
41
|
+
when object.blank? || is_encrypted?(object)
|
42
|
+
object
|
43
|
+
else
|
44
|
+
convert(object).mongoize
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Converts the object that was supplied to a criteria and converts it into a database friendly form.
|
49
|
+
alias_method :evolve, :mongoize
|
50
|
+
|
51
|
+
def convert(object)
|
52
|
+
raise NotImplementedError.new("convert must be implemented")
|
53
|
+
end
|
54
|
+
|
55
|
+
# Used to identify encrypted strings
|
56
|
+
MARKER = Base64.encode64('\x02`{~MeF~}`\x03').chomp
|
57
|
+
|
58
|
+
def encrypt(plaintext)
|
59
|
+
encrypted = EncryptedFields.cipher.encrypt(plaintext).chomp
|
60
|
+
MARKER + encrypted
|
61
|
+
end
|
62
|
+
|
63
|
+
def decrypt(encrypted)
|
64
|
+
unmarked = encrypted.slice(MARKER.size..-1)
|
65
|
+
EncryptedFields.cipher.decrypt(unmarked)
|
66
|
+
end
|
67
|
+
|
68
|
+
def is_encrypted?(object)
|
69
|
+
object.is_a?(::String) && object.start_with?(MARKER)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Used to store an encrypted string in Mongo
|
4
|
+
#
|
5
|
+
# Usage:
|
6
|
+
# field :social_security_number, type: Mongoid::EncryptedString
|
7
|
+
#
|
8
|
+
# Set with an unencrypted string
|
9
|
+
# p = Person.new()
|
10
|
+
# p.social_security_number = '123456789'
|
11
|
+
#
|
12
|
+
# Get returns the unencrypted string
|
13
|
+
# puts p.social_security_number -> '123456789'
|
14
|
+
#
|
15
|
+
# Use the encrypted property to see the encrypted value
|
16
|
+
# puts p.social_security_number.encrypted -> '....'
|
17
|
+
#
|
18
|
+
module Mongoid
|
19
|
+
class EncryptedString < ::String
|
20
|
+
include Mongoid::EncryptedField
|
21
|
+
|
22
|
+
class << self
|
23
|
+
|
24
|
+
def convert(object)
|
25
|
+
new(object)
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Used to store an encrypted time in Mongo
|
3
|
+
#
|
4
|
+
# Usage:
|
5
|
+
# field :birth_date, type: Mongoid::EncryptedDate
|
6
|
+
#
|
7
|
+
# Set with an unencrypted date
|
8
|
+
# p = Person.new()
|
9
|
+
# p.birth_date = Date.new(2000, 1, 1)
|
10
|
+
#
|
11
|
+
# Get returns the unencrypted date
|
12
|
+
# puts p.birth_date -> 'Jan 1, 2000'
|
13
|
+
#
|
14
|
+
# Use the encrypted property to see the encrypted value
|
15
|
+
# puts p.birth_date.encrypted -> '....'
|
16
|
+
#
|
17
|
+
module Mongoid
|
18
|
+
class EncryptedTime < ::Time
|
19
|
+
include Mongoid::EncryptedField
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
def from_time(time)
|
24
|
+
parse(time.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert(object)
|
28
|
+
from_time(object.to_time)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Mongoid
|
2
|
+
module EncryptedFields
|
3
|
+
# Contains behavior for logging.
|
4
|
+
module Logging
|
5
|
+
|
6
|
+
# Get the logger.
|
7
|
+
#
|
8
|
+
# @example Get the logger.
|
9
|
+
# Logging.logger
|
10
|
+
#
|
11
|
+
# @return [ Logger ] The logger.
|
12
|
+
#
|
13
|
+
# @since 1.0.0
|
14
|
+
def logger
|
15
|
+
return @logger if defined?(@logger)
|
16
|
+
@logger = rails_logger || default_logger
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get the rails logger.
|
20
|
+
#
|
21
|
+
# @example Get the rails logger.
|
22
|
+
# Logging.rails_logger
|
23
|
+
#
|
24
|
+
# @return [ Logger ] The Rails logger.
|
25
|
+
#
|
26
|
+
# @since 1.0.0
|
27
|
+
def rails_logger
|
28
|
+
defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get the default logger.
|
32
|
+
#
|
33
|
+
# @example Get the default logger.
|
34
|
+
# Logging.default_logger
|
35
|
+
#
|
36
|
+
# @return [ Logger ] The default logger.
|
37
|
+
#
|
38
|
+
# @since 1.0.0
|
39
|
+
def default_logger
|
40
|
+
logger = Logger.new(STDOUT)
|
41
|
+
logger.level = Logger::INFO
|
42
|
+
logger
|
43
|
+
end
|
44
|
+
|
45
|
+
# Set the logger.
|
46
|
+
#
|
47
|
+
# @example Set the logger.
|
48
|
+
# Logging.logger = logger
|
49
|
+
#
|
50
|
+
# @return [ Logger ] The logger.
|
51
|
+
#
|
52
|
+
# @since 1.0.0
|
53
|
+
def logger=(logger)
|
54
|
+
@logger = logger
|
55
|
+
end
|
56
|
+
end
|
57
|
+
extend Logging
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'mongoid-encrypted-fields/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "mongoid-encrypted-fields"
|
8
|
+
gem.version = Mongoid::EncryptedFields::VERSION
|
9
|
+
gem.authors = ["Koan Health"]
|
10
|
+
gem.email = ["development@koanhealth.com"]
|
11
|
+
gem.description = "A library for storing encrypted data in Mongo"
|
12
|
+
gem.summary = "Custom types for storing encrypted data"
|
13
|
+
gem.homepage = "https://github.com/KoanHealth/mongoid-encrypted-fields"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency "rake"
|
21
|
+
gem.add_dependency "mongoid", "~> 3"
|
22
|
+
gem.add_dependency "encrypted_strings", "~> 0.3"
|
23
|
+
|
24
|
+
gem.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe EncryptedDate do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: 'my test password')
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { Mongoid::EncryptedDate }
|
11
|
+
let(:raw) { Date.today }
|
12
|
+
let(:encrypted) { Mongoid::EncryptedDate.mongoize(Date.today) }
|
13
|
+
|
14
|
+
it "returns the same date" do
|
15
|
+
subject.from_date(raw).should eq(raw)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should encrypt the date" do
|
19
|
+
subject.from_date(raw).encrypted.should eq(encrypted)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "nil should fail" do
|
23
|
+
-> { subject.from_date(nil) }.should raise_error()
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "demongoize" do
|
27
|
+
|
28
|
+
it "nil should return nil" do
|
29
|
+
subject.demongoize(nil).should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "blank should return itself" do
|
33
|
+
subject.demongoize('').should eq('')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "invalid date should fail" do
|
37
|
+
-> { subject.demongoize('not a date') }.should raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
it "encrypted date should return unencrypted date" do
|
41
|
+
decrypted = subject.demongoize(encrypted)
|
42
|
+
decrypted.is_a?(subject).should be_true
|
43
|
+
decrypted.should eq(raw)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "mongoize" do
|
49
|
+
|
50
|
+
it "encrypted date should return encrypted" do
|
51
|
+
subject.mongoize(subject.from_date(raw)).should eq(encrypted)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "encrypted date should return itself" do
|
55
|
+
subject.mongoize(encrypted).should eq(encrypted)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "nil should return nil" do
|
59
|
+
subject.mongoize(nil).should eq(nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "non empty date should return encrypted" do
|
63
|
+
subject.mongoize(raw).should eq(encrypted)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe EncryptedDateTime do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: 'my test password')
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { Mongoid::EncryptedDateTime }
|
11
|
+
let(:raw) { DateTime.new(2010, 6, 15, 1, 2, 3) }
|
12
|
+
let(:encrypted) { Mongoid::EncryptedDateTime.mongoize(DateTime.new(2010, 6, 15, 1, 2, 3)) }
|
13
|
+
|
14
|
+
it "returns the same datetime" do
|
15
|
+
subject.from_datetime(raw).should eq(raw)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should encrypt the datetime" do
|
19
|
+
subject.from_datetime(raw).encrypted.should eq(encrypted)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "nil should fail" do
|
23
|
+
-> { subject.from_datetime(nil) }.should raise_error()
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "demongoize" do
|
27
|
+
|
28
|
+
it "nil should return nil" do
|
29
|
+
subject.demongoize(nil).should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "blank string should return blank string" do
|
33
|
+
subject.demongoize('').should eq('')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "invalid datetime should fail" do
|
37
|
+
-> { subject.demongoize('not a date') }.should raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
it "encrypted datetime should return unencrypted datetime" do
|
41
|
+
decrypted = subject.demongoize(encrypted)
|
42
|
+
decrypted.is_a?(subject).should be_true
|
43
|
+
decrypted.should eq(raw)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "mongoize" do
|
49
|
+
|
50
|
+
it "encrypted datetime should return encrypted" do
|
51
|
+
subject.mongoize(subject.from_datetime(raw)).should eq(encrypted)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "encrypted datetime should return itself" do
|
55
|
+
subject.mongoize(encrypted).should eq(encrypted)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "nil should return nil" do
|
59
|
+
subject.mongoize(nil).should eq(nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "valid datetime should return encrypted" do
|
63
|
+
subject.mongoize(raw).should eq(encrypted)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe EncryptedField do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: 'my test password')
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should encrypt and decrypt a string" do
|
11
|
+
plaintext = "this is a test!"
|
12
|
+
encrypted = Mongoid::EncryptedString.encrypt(plaintext)
|
13
|
+
unencrypted = Mongoid::EncryptedString.decrypt(encrypted)
|
14
|
+
|
15
|
+
unencrypted.should eq(plaintext)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should be able to identify an encrypted string" do
|
19
|
+
plaintext = "this is a test!"
|
20
|
+
Mongoid::EncryptedString.is_encrypted?(plaintext).should be_false
|
21
|
+
|
22
|
+
encrypted = Mongoid::EncryptedString.encrypt(plaintext)
|
23
|
+
Mongoid::EncryptedString.is_encrypted?(encrypted).should be_true
|
24
|
+
|
25
|
+
unencrypted = Mongoid::EncryptedString.decrypt(encrypted)
|
26
|
+
Mongoid::EncryptedString.is_encrypted?(unencrypted).should be_false
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe EncryptedString do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: 'my test password')
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:raw) { "abc123" }
|
11
|
+
let(:encrypted) { Mongoid::EncryptedString.new("abc123").encrypted }
|
12
|
+
|
13
|
+
it "returns the same string" do
|
14
|
+
Mongoid::EncryptedString.new(raw).should eq(raw)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should encrypt the string" do
|
18
|
+
Mongoid::EncryptedString.new(raw).encrypted.should eq(encrypted)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "nil should fail" do
|
22
|
+
-> { Mongoid::EncryptedString.new(nil) }.should raise_error()
|
23
|
+
end
|
24
|
+
|
25
|
+
it "modified string automatically encrypts after change" do
|
26
|
+
str = "this is a test"
|
27
|
+
es = Mongoid::EncryptedString.new(str)
|
28
|
+
es.encrypted.should eq(Mongoid::EncryptedString.encrypt(str))
|
29
|
+
|
30
|
+
es.chop!
|
31
|
+
es.encrypted.should eq(Mongoid::EncryptedString.encrypt(str.chop))
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "demongoize" do
|
35
|
+
|
36
|
+
it "nil should return nil" do
|
37
|
+
Mongoid::EncryptedString.demongoize(nil).should be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "empty string should return empty string" do
|
41
|
+
Mongoid::EncryptedString.demongoize('').should eq('')
|
42
|
+
end
|
43
|
+
|
44
|
+
it "encrypted string should return instance of Mongoid::EncryptedString" do
|
45
|
+
Mongoid::EncryptedString.demongoize(encrypted).is_a?(Mongoid::EncryptedString).should be_true
|
46
|
+
end
|
47
|
+
|
48
|
+
it "encrypted string should return unencrypted string" do
|
49
|
+
Mongoid::EncryptedString.demongoize(encrypted).should eq(raw)
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
describe "mongoize" do
|
55
|
+
|
56
|
+
it "encrypted string should return encrypted" do
|
57
|
+
Mongoid::EncryptedString.mongoize(Mongoid::EncryptedString.new(raw)).should eq(encrypted)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "encrypted string should return itself" do
|
61
|
+
Mongoid::EncryptedString.mongoize(encrypted).should eq(encrypted)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "nil should return nil" do
|
65
|
+
Mongoid::EncryptedString.mongoize(nil).should eq(nil)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "empty string should return empty string" do
|
69
|
+
Mongoid::EncryptedString.mongoize('').should eq('')
|
70
|
+
end
|
71
|
+
|
72
|
+
it "non empty string should return encrypted" do
|
73
|
+
Mongoid::EncryptedString.mongoize(raw).should eq(encrypted)
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Mongoid
|
4
|
+
describe EncryptedTime do
|
5
|
+
|
6
|
+
before(:all) do
|
7
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: 'my test password')
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { Mongoid::EncryptedTime }
|
11
|
+
let(:raw) { Time.at(946702800) }
|
12
|
+
let(:encrypted) { Mongoid::EncryptedTime.mongoize(Time.at(946702800)) }
|
13
|
+
|
14
|
+
it "returns the same time" do
|
15
|
+
subject.from_time(raw).should eq(raw)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should encrypt the time" do
|
19
|
+
subject.from_time(raw).encrypted.should eq(encrypted)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "nil should fail" do
|
23
|
+
-> { subject.from_time(nil) }.should raise_error()
|
24
|
+
end
|
25
|
+
|
26
|
+
describe "demongoize" do
|
27
|
+
|
28
|
+
it "nil should return nil" do
|
29
|
+
subject.demongoize(nil).should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it "blank should return itself" do
|
33
|
+
subject.demongoize('').should eq('')
|
34
|
+
end
|
35
|
+
|
36
|
+
it "invalid time should fail" do
|
37
|
+
-> { subject.demongoize('not a time') }.should raise_error
|
38
|
+
end
|
39
|
+
|
40
|
+
it "encrypted time should return unencrypted time" do
|
41
|
+
decrypted = subject.demongoize(encrypted)
|
42
|
+
decrypted.is_a?(subject).should be_true
|
43
|
+
decrypted.should eq(raw)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "mongoize" do
|
49
|
+
|
50
|
+
it "encrypted time should return encrypted" do
|
51
|
+
subject.mongoize(subject.from_time(raw)).should eq(encrypted)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "encrypted time should return itself" do
|
55
|
+
subject.mongoize(encrypted).should eq(encrypted)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "nil should return nil" do
|
59
|
+
subject.mongoize(nil).should eq(nil)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "non empty time should return encrypted" do
|
63
|
+
subject.mongoize(raw).should eq(encrypted)
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Single model' do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
Mongoid::EncryptedFields.cipher = Mongoid::Ciphers::SymmetricCipher.new(algorithm: 'aes-256-cbc', password: 'my test password')
|
7
|
+
end
|
8
|
+
|
9
|
+
let (:ssn) { '123456789' }
|
10
|
+
let (:ssn_encrypted) { Mongoid::EncryptedString.mongoize('123456789') }
|
11
|
+
let (:birth_date) { 20.years.ago.to_date }
|
12
|
+
let (:birth_date_encrypted) { Mongoid::EncryptedDate.mongoize(20.years.ago.to_date) }
|
13
|
+
let (:person) { Person.new(name: 'John Doe', ssn: '123456789', birth_date: 20.years.ago.to_date) }
|
14
|
+
|
15
|
+
it "model stores encrypted ssn" do
|
16
|
+
person.attributes['ssn'].should eq(ssn_encrypted)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "model stores encrypted birth_date" do
|
20
|
+
person.attributes['birth_date'].should eq(birth_date_encrypted)
|
21
|
+
end
|
22
|
+
|
23
|
+
it "ssn getter returns raw value" do
|
24
|
+
person.ssn.should eq(ssn)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "birth_date getter returns raw value" do
|
28
|
+
person.birth_date.should eq(birth_date)
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "after save" do
|
32
|
+
|
33
|
+
before(:each) do
|
34
|
+
Mongoid.purge!
|
35
|
+
person.save!
|
36
|
+
@persisted = Person.find(person.id)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "encrypted ssn is persisted" do
|
40
|
+
@persisted.attributes['ssn'].should eq(ssn_encrypted)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "encrypted birth_date is persisted" do
|
44
|
+
@persisted.attributes['birth_date'].should eq(birth_date_encrypted)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "encrypted ssn getter returns raw value" do
|
48
|
+
@persisted.ssn.should eq(ssn)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "encrypted birth_date getter returns raw value" do
|
52
|
+
@persisted.birth_date.should eq(birth_date)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
describe "find model by encrypted field" do
|
58
|
+
|
59
|
+
before(:each) do
|
60
|
+
Mongoid.purge!
|
61
|
+
person.save!
|
62
|
+
end
|
63
|
+
|
64
|
+
it "ssn matches equality" do
|
65
|
+
Person.where(ssn: ssn).count.should eq(1)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "birth date matches equality" do
|
69
|
+
Person.where(birth_date: birth_date).count.should eq(1)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'mongoid'
|
4
|
+
require 'encrypted_strings'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
require 'mongoid-encrypted-fields'
|
8
|
+
|
9
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
10
|
+
|
11
|
+
ENV['MONGOID_ENV'] ||= 'test'
|
12
|
+
Mongoid.load!("#{File.dirname(__FILE__)}/config/mongoid.yml")
|
13
|
+
|
14
|
+
Mongoid::EncryptedFields.logger.level = Logger::DEBUG
|
15
|
+
|
16
|
+
RSpec.configure do |config|
|
17
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
18
|
+
config.run_all_when_everything_filtered = true
|
19
|
+
config.filter_run :focus
|
20
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mongoid-encrypted-fields
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Koan Health
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rake
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: mongoid
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '3'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '3'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: encrypted_strings
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.3'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.3'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rspec
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
description: A library for storing encrypted data in Mongo
|
79
|
+
email:
|
80
|
+
- development@koanhealth.com
|
81
|
+
executables: []
|
82
|
+
extensions: []
|
83
|
+
extra_rdoc_files: []
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- .rspec
|
87
|
+
- .rvmrc
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE.txt
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- lib/mongoid-encrypted-fields.rb
|
93
|
+
- lib/mongoid-encrypted-fields/ciphers/asymmetric_cipher.rb
|
94
|
+
- lib/mongoid-encrypted-fields/ciphers/cipher.rb
|
95
|
+
- lib/mongoid-encrypted-fields/ciphers/symmetric_cipher.rb
|
96
|
+
- lib/mongoid-encrypted-fields/fields/encrypted_date.rb
|
97
|
+
- lib/mongoid-encrypted-fields/fields/encrypted_date_time.rb
|
98
|
+
- lib/mongoid-encrypted-fields/fields/encrypted_field.rb
|
99
|
+
- lib/mongoid-encrypted-fields/fields/encrypted_string.rb
|
100
|
+
- lib/mongoid-encrypted-fields/fields/encrypted_time.rb
|
101
|
+
- lib/mongoid-encrypted-fields/logging.rb
|
102
|
+
- lib/mongoid-encrypted-fields/version.rb
|
103
|
+
- mongoid-encrypted-fields.gemspec
|
104
|
+
- spec/config/mongoid.yml
|
105
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_date_spec.rb
|
106
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_datetime_spec.rb
|
107
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_field_spec.rb
|
108
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_string_spec.rb
|
109
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_time_spec.rb
|
110
|
+
- spec/mongoid-encrypted-fields/fields/model_spec.rb
|
111
|
+
- spec/spec_helper.rb
|
112
|
+
- spec/support/models/person.rb
|
113
|
+
homepage: https://github.com/KoanHealth/mongoid-encrypted-fields
|
114
|
+
licenses: []
|
115
|
+
post_install_message:
|
116
|
+
rdoc_options: []
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ! '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
segments:
|
126
|
+
- 0
|
127
|
+
hash: 3554219364353199264
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
segments:
|
135
|
+
- 0
|
136
|
+
hash: 3554219364353199264
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 1.8.24
|
140
|
+
signing_key:
|
141
|
+
specification_version: 3
|
142
|
+
summary: Custom types for storing encrypted data
|
143
|
+
test_files:
|
144
|
+
- spec/config/mongoid.yml
|
145
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_date_spec.rb
|
146
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_datetime_spec.rb
|
147
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_field_spec.rb
|
148
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_string_spec.rb
|
149
|
+
- spec/mongoid-encrypted-fields/fields/encrypted_time_spec.rb
|
150
|
+
- spec/mongoid-encrypted-fields/fields/model_spec.rb
|
151
|
+
- spec/spec_helper.rb
|
152
|
+
- spec/support/models/person.rb
|