mongoid-encrypted-fields 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,20 @@
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
+
19
+ #Ignore IDEA directory
20
+ /.idea
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use 1.9.3@mongoid-encrypted-fields --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in mongoid-encrypted-fields.gemspec
4
+ gemspec
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,5 @@
1
+ module Mongoid
2
+ module EncryptedFields
3
+ VERSION = "1.0.0"
4
+ end
5
+ 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,9 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: mongoid_encrypted_fields_test
5
+ hosts:
6
+ - localhost:27017
7
+ options:
8
+ safe: true
9
+ consistency: :strong
@@ -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
@@ -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
@@ -0,0 +1,8 @@
1
+ class Person
2
+ include Mongoid::Document
3
+
4
+ field :name, type: String
5
+ field :ssn, type: Mongoid::EncryptedString
6
+ field :birth_date, type: Mongoid::EncryptedDate
7
+
8
+ 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