From 27578df13258c9658a98c5f707437b3ee7b2be19 Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Tue, 2 Oct 2018 11:34:51 +0100 Subject: [PATCH 001/143] Add initial CircleCI config --- .circleci/config.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..d74d0845 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,22 @@ +version: 2 +jobs: + tests: + docker: + - image: ruby:2.4.4-alpine + environment: + RACK_ENV: test + VAULT_VERSION: 0.6.0 + steps: + - checkout + - run: apk add --no-cache build-base sqlite-dev tzdata + - run: + name: Install Vault + command: | + wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + unzip vault.zip + mv vault /usr/local/bin/ + - run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle + - run: bundle exec rake app:db:create + - run: bundle exec rake app:db:schema:load + - run: bundle exec rake app:db:test:prepare + - run: bundle exec rspec From 831f397124e6df0997237a4482e6c19e2c531c94 Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Tue, 2 Oct 2018 11:42:39 +0100 Subject: [PATCH 002/143] Publish pre-release gem versions to JFrog --- .circleci/config.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index d74d0845..1f8111c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -20,3 +20,43 @@ jobs: - run: bundle exec rake app:db:schema:load - run: bundle exec rake app:db:test:prepare - run: bundle exec rspec + publish-pre-release: + docker: + - image: ruby:2.4.4-alpine + steps: + - checkout + - run: + name: Install cURL + command: apk add --no-cache curl + - run: + name: Login to JFrog + command: + mkdir -p ~/.gem + curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials + chmod 600 ~/.gem/credentials + - run: + name: Install Gem Versioner + command: gem install gem-versioner --version '~> 1.0' --no-document + - run: + name: Build gem + command: | + PRE_RELEASE="$CIRCLE_BRANCH" gem build "$CIRCLE_PROJECT_REPONAME".gemspec + - run: + name: Publish gem + command: | + package=$(ls -t1 "$CIRCLE_PROJECT_REPONAME"-*.gem | head -1) + gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases +workflows: + version: 2 + test-then-publish: + jobs: + - tests + - publish-pre-release: + context: org-global + filters: + branches: + ignore: master + tags: + ignore: /.*/ + requires: + - tests From 5d32480fb12ab52160cd73a72fa32f386da44c33 Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Tue, 2 Oct 2018 11:50:42 +0100 Subject: [PATCH 003/143] Cache dependencies --- .circleci/config.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1f8111c6..4fc1188b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,14 @@ jobs: wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip unzip vault.zip mv vault /usr/local/bin/ + - restore_cache: + keys: + - vault-rails-{{checksum "vault-rails.gemspec" }} - run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle + - save_cache: + key: vault-rails-{{checksum "vault-rails.gemspec" }} + paths: + - vendor/bundle - run: bundle exec rake app:db:create - run: bundle exec rake app:db:schema:load - run: bundle exec rake app:db:test:prepare From ef7afb6111c6aedc865c387e237ccc3510158c9f Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 2 Oct 2018 14:35:58 +0300 Subject: [PATCH 004/143] CircleCI integration * Fix configuration * Update Vault version * Add config so a new release is published when a tag is pushed to Github --- .circleci/config.yml | 56 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 4fc1188b..494239f5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,7 @@ jobs: - image: ruby:2.4.4-alpine environment: RACK_ENV: test - VAULT_VERSION: 0.6.0 + VAULT_VERSION: 0.10.0 steps: - checkout - run: apk add --no-cache build-base sqlite-dev tzdata @@ -17,17 +17,45 @@ jobs: mv vault /usr/local/bin/ - restore_cache: keys: - - vault-rails-{{checksum "vault-rails.gemspec" }} + - vault-rails-{{checksum "vault.gemspec" }} - run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle - save_cache: - key: vault-rails-{{checksum "vault-rails.gemspec" }} + key: vault-rails-{{checksum "vault.gemspec" }} paths: - vendor/bundle - run: bundle exec rake app:db:create - run: bundle exec rake app:db:schema:load - run: bundle exec rake app:db:test:prepare - run: bundle exec rspec + publish-pre-release: + docker: + - image: ruby:2.4.4-alpine + steps: + - checkout + - run: + name: Install cURL + command: apk add --no-cache curl + - run: + name: Login to JFrog + command: | + mkdir -p ~/.gem + curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials + chmod 600 ~/.gem/credentials + - run: + name: Install Gem Versioner + command: gem install gem-versioner --version '~> 1.0' --no-document + - run: + name: Build gem + command: | + PRE_RELEASE="$CIRCLE_BRANCH" gem build vault.gemspec + - run: + name: Publish gem + command: | + package=$(ls -t1 vault-rails-*.gem | head -1) + gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases + + publish-release: docker: - image: ruby:2.4.4-alpine steps: @@ -37,7 +65,7 @@ jobs: command: apk add --no-cache curl - run: name: Login to JFrog - command: + command: | mkdir -p ~/.gem curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials chmod 600 ~/.gem/credentials @@ -47,15 +75,17 @@ jobs: - run: name: Build gem command: | - PRE_RELEASE="$CIRCLE_BRANCH" gem build "$CIRCLE_PROJECT_REPONAME".gemspec + gem build vault.gemspec - run: name: Publish gem command: | - package=$(ls -t1 "$CIRCLE_PROJECT_REPONAME"-*.gem | head -1) - gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases + package=$(ls -t1 vault-rails-*.gem | head -1) + gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local + workflows: version: 2 - test-then-publish: + + test-and-pre-release: jobs: - tests - publish-pre-release: @@ -67,3 +97,13 @@ workflows: ignore: /.*/ requires: - tests + + release: + jobs: + - publish-release: + context: org-global + filters: + branches: + ignore: /.*/ + tags: + only: /^v[0-9]+(\.[0-9]+)*$/ From 6f46db927fff0f30fa88732e82314f0752b7f1d0 Mon Sep 17 00:00:00 2001 From: Sasha Gerrand Date: Tue, 2 Oct 2018 21:29:05 +0100 Subject: [PATCH 005/143] Require tests to pass for tagged releases As a tag could potentially originate from any commit, run the test suite for any changes. They should pass prior to building and pushing a released gem. --- .circleci/config.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 494239f5..47e92824 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,9 +85,12 @@ jobs: workflows: version: 2 - test-and-pre-release: + test-and-release: jobs: - - tests + - tests: + filters: + tags: + only: /.*/ - publish-pre-release: context: org-global filters: @@ -97,9 +100,6 @@ workflows: ignore: /.*/ requires: - tests - - release: - jobs: - publish-release: context: org-global filters: @@ -107,3 +107,5 @@ workflows: ignore: /.*/ tags: only: /^v[0-9]+(\.[0-9]+)*$/ + requires: + - tests From f3705dbe827de65c04f32f3e6b53b4f26a10293e Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Thu, 4 Oct 2018 11:02:34 +0300 Subject: [PATCH 006/143] Fix tag builds on CircleCI * Add git to publish-release in .circleci/config.yml for fix builds * Fix indentation in .circleci/config.yml --- .circleci/config.yml | 47 ++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 47e92824..24dab78f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -32,28 +32,28 @@ jobs: docker: - image: ruby:2.4.4-alpine steps: - - checkout - - run: - name: Install cURL - command: apk add --no-cache curl - - run: - name: Login to JFrog - command: | - mkdir -p ~/.gem - curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials - chmod 600 ~/.gem/credentials - - run: - name: Install Gem Versioner - command: gem install gem-versioner --version '~> 1.0' --no-document - - run: - name: Build gem - command: | - PRE_RELEASE="$CIRCLE_BRANCH" gem build vault.gemspec - - run: - name: Publish gem - command: | - package=$(ls -t1 vault-rails-*.gem | head -1) - gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases + - checkout + - run: + name: Install cURL + command: apk add --no-cache curl + - run: + name: Login to JFrog + command: | + mkdir -p ~/.gem + curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials + chmod 600 ~/.gem/credentials + - run: + name: Install Gem Versioner + command: gem install gem-versioner --version '~> 1.0' --no-document + - run: + name: Build gem + command: | + PRE_RELEASE="$CIRCLE_BRANCH" gem build vault.gemspec + - run: + name: Publish gem + command: | + package=$(ls -t1 vault-rails-*.gem | head -1) + gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases publish-release: docker: @@ -62,7 +62,7 @@ jobs: - checkout - run: name: Install cURL - command: apk add --no-cache curl + command: apk add --no-cache curl git - run: name: Login to JFrog command: | @@ -100,6 +100,7 @@ workflows: ignore: /.*/ requires: - tests + - publish-release: context: org-global filters: From ba2de1ff88d2b4a211147aa62ccedeadd1465f34 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 15 Aug 2018 17:46:55 +0300 Subject: [PATCH 007/143] Persist attributes before save Persisting Vault attributes on an `after_save` uses two separate queries: one for the model `INSERT/UPDATE`, and another to `UPDATE` the ciphertext for the encrypted attributes. Encrypting the attributes with a `before_save` avoids the second query. In some cases users might _not_ want to have two queries when saving a single record. This would be necessary for example, when one has an auditing table and/or stored procedures that take some action when a record is changed. --- lib/vault/encrypted_model.rb | 30 +++++++++++------ spec/unit/encrypted_model_spec.rb | 53 +++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 9 deletions(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 64441ed1..924e44e3 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -116,6 +116,12 @@ def vault_attribute(attribute, options = {}) self end + # Encrypt Vault attribures before saving them + def vault_persist_before_save! + skip_callback :save, :after, :__vault_persist_attributes! + before_save :__vault_encrypt_attributes! + end + # The list of Vault attributes. # # @return [Hash] @@ -219,13 +225,7 @@ def __vault_load_attribute!(attribute, options) # on this model. # @return [true] def __vault_persist_attributes! - changes = {} - - self.class.__vault_attributes.each do |attribute, options| - if c = self.__vault_persist_attribute!(attribute, options) - changes.merge!(c) - end - end + changes = __vault_encrypt_attributes! # If there are any changes to the model, update them all at once, # skipping any callbacks and validation. This is okay, because we are @@ -234,12 +234,24 @@ def __vault_persist_attributes! self.update_columns(changes) end - return true + true + end + + def __vault_encrypt_attributes! + changes = {} + + self.class.__vault_attributes.each do |attribute, options| + if c = self.__vault_encrypt_attribute!(attribute, options) + changes.merge!(c) + end + end + + changes end # Encrypt a single attribute using Vault and persist back onto the # encrypted attribute value. - def __vault_persist_attribute!(attribute, options) + def __vault_encrypt_attribute!(attribute, options) key = options[:key] path = options[:path] serializer = options[:serializer] diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 46c37f07..09d54ac9 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -42,4 +42,57 @@ expect(klass.instance_methods).to include(:foo_was) end end + + describe '#vault_persist_before_save!' do + let(:after_save_dummy_class) do + Class.new(ActiveRecord::Base) do + include Vault::EncryptedModel + end + end + + let(:before_save_dummy_class) do + Class.new(ActiveRecord::Base) do + include Vault::EncryptedModel + vault_persist_before_save! + end + end + + context "when not used" do + it "the model has an after_save callback" do + save_callbacks = after_save_dummy_class._save_callbacks.select do |cb| + cb.filter == :__vault_persist_attributes! + end + + expect(save_callbacks.length).to eq 1 + persist_callback = save_callbacks.first + + expect(persist_callback).to be_a ActiveSupport::Callbacks::Callback + + expect(persist_callback.kind).to eq :after + end + end + + context "when used" do + it "the model does not have a after_save callback" do + save_callbacks = before_save_dummy_class._save_callbacks.select do |cb| + cb.filter == :__vault_persist_attributes! + end + + expect(save_callbacks.length).to eq 0 + end + + it "the model has a before_save callback" do + save_callbacks = before_save_dummy_class._save_callbacks.select do |cb| + cb.filter == :__vault_encrypt_attributes! + end + + expect(save_callbacks.length).to eq 1 + persist_callback = save_callbacks.first + + expect(persist_callback).to be_a ActiveSupport::Callbacks::Callback + + expect(persist_callback.kind).to eq :before + end + end + end end From 7cac8c705e71ea8f4d92dfcd3375cb28b758dd3c Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Fri, 5 Oct 2018 11:22:18 +0300 Subject: [PATCH 008/143] Add serializers for Dates, Integers and Floats --- lib/vault/rails.rb | 26 ++++++++------ lib/vault/rails/errors.rb | 17 ++++----- lib/vault/rails/serializer.rb | 33 ----------------- .../rails/serializers/date_serializer.rb | 22 ++++++++++++ .../rails/serializers/float_serializer.rb | 21 +++++++++++ .../rails/serializers/integer_serializer.rb | 19 ++++++++++ .../rails/serializers/json_serializer.rb | 35 +++++++++++++++++++ .../rails/serializers/date_serializer_spec.rb | 12 +++++++ .../serializers/float_serializer_spec.rb | 15 ++++++++ .../serializers/integer_serializer_spec.rb | 15 ++++++++ spec/unit/rails_spec.rb | 20 +++++------ 11 files changed, 174 insertions(+), 61 deletions(-) delete mode 100644 lib/vault/rails/serializer.rb create mode 100644 lib/vault/rails/serializers/date_serializer.rb create mode 100644 lib/vault/rails/serializers/float_serializer.rb create mode 100644 lib/vault/rails/serializers/integer_serializer.rb create mode 100644 lib/vault/rails/serializers/json_serializer.rb create mode 100644 spec/unit/rails/serializers/date_serializer_spec.rb create mode 100644 spec/unit/rails/serializers/float_serializer_spec.rb create mode 100644 spec/unit/rails/serializers/integer_serializer_spec.rb diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 032e71be..3a0bc47f 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -1,13 +1,16 @@ -require "vault" +require 'vault' -require "base64" -require "json" +require 'base64' +require 'json' -require_relative "encrypted_model" -require_relative "rails/configurable" -require_relative "rails/errors" -require_relative "rails/serializer" -require_relative "rails/version" +require_relative 'encrypted_model' +require_relative 'rails/configurable' +require_relative 'rails/errors' +require_relative 'rails/serializers/json_serializer' +require_relative 'rails/serializers/date_serializer' +require_relative 'rails/serializers/integer_serializer' +require_relative 'rails/serializers/float_serializer' +require_relative 'rails/version' module Vault module Rails @@ -15,7 +18,10 @@ module Rails # # @return [Hash] SERIALIZERS = { - json: Vault::Rails::JSONSerializer, + json: Vault::Rails::Serializers::JSONSerializer, + date: Vault::Rails::Serializers::DateSerializer, + integer: Vault::Rails::Serializers::IntegerSerializer, + float: Vault::Rails::Serializers::FloatSerializer }.freeze # The default encoding. @@ -136,7 +142,7 @@ def serializer_for(key) if serializer = SERIALIZERS[key] return serializer else - raise Vault::Rails::UnknownSerializerError.new(key) + raise Vault::Rails::Serializers::UnknownSerializerError.new(key) end end diff --git a/lib/vault/rails/errors.rb b/lib/vault/rails/errors.rb index 73fe0d62..8ed46963 100644 --- a/lib/vault/rails/errors.rb +++ b/lib/vault/rails/errors.rb @@ -1,19 +1,20 @@ module Vault module Rails class VaultRailsError < RuntimeError; end + class ValidationFailedError < VaultRailsError; end - class UnknownSerializerError < VaultRailsError - def initialize(key) - super <<-EOH - Unknown Vault serializer `:#{key}'. Valid serializers are: + module Serializers + class UnknownSerializerError < VaultRailsError + def initialize(key) + super <<-EOH + Unknown Vault serializer `:#{key}`. Valid serializers are: - #{SERIALIZERS.keys.sort.map(&:inspect).join(", ")} + #{SERIALIZERS.keys.sort.map(&:inspect).join(", ")} Please refer to the documentation for more examples. - EOH + EOH + end end end - - class ValidationFailedError < VaultRailsError; end end end diff --git a/lib/vault/rails/serializer.rb b/lib/vault/rails/serializer.rb deleted file mode 100644 index 26f7114e..00000000 --- a/lib/vault/rails/serializer.rb +++ /dev/null @@ -1,33 +0,0 @@ -module Vault - module Rails - module JSONSerializer - DECODE_OPTIONS = { - max_nested: false, - create_additions: false, - }.freeze - - def self.encode(raw) - self._init! - - raw = {} if raw.nil? - - JSON.fast_generate(raw) - end - - def self.decode(raw) - self._init! - - return {} if raw.nil? || raw.empty? - JSON.parse(raw, DECODE_OPTIONS) - end - - protected - - def self._init! - return if defined?(@_init) - require "json" - @_init = true - end - end - end -end diff --git a/lib/vault/rails/serializers/date_serializer.rb b/lib/vault/rails/serializers/date_serializer.rb new file mode 100644 index 00000000..bfb750b2 --- /dev/null +++ b/lib/vault/rails/serializers/date_serializer.rb @@ -0,0 +1,22 @@ +module Vault + module Rails + module Serializers + # Converts date objects to and from ISO 8601 format (%F) + module DateSerializer + module_function + + def encode(raw) + return nil if raw.blank? + + raw = Date.parse(raw) if raw.is_a? String + raw.strftime('%F') + end + + def decode(raw) + return nil if raw.blank? + Date.strptime(raw, '%F') + end + end + end + end +end diff --git a/lib/vault/rails/serializers/float_serializer.rb b/lib/vault/rails/serializers/float_serializer.rb new file mode 100644 index 00000000..f87a3443 --- /dev/null +++ b/lib/vault/rails/serializers/float_serializer.rb @@ -0,0 +1,21 @@ +module Vault + module Rails + module Serializers + module FloatSerializer + module_function + + def encode(raw) + return nil if raw.blank? + raw.to_s + end + + def decode(raw) + return nil if raw.blank? + raw.to_f + end + end + end + end +end + + diff --git a/lib/vault/rails/serializers/integer_serializer.rb b/lib/vault/rails/serializers/integer_serializer.rb new file mode 100644 index 00000000..16227de2 --- /dev/null +++ b/lib/vault/rails/serializers/integer_serializer.rb @@ -0,0 +1,19 @@ +module Vault + module Rails + module Serializers + module IntegerSerializer + module_function + + def encode(raw) + return nil if raw.blank? + raw.to_s + end + + def decode(raw) + return nil if raw.blank? + raw.to_i + end + end + end + end +end diff --git a/lib/vault/rails/serializers/json_serializer.rb b/lib/vault/rails/serializers/json_serializer.rb new file mode 100644 index 00000000..f85337b3 --- /dev/null +++ b/lib/vault/rails/serializers/json_serializer.rb @@ -0,0 +1,35 @@ +module Vault + module Rails + module Serializers + module JSONSerializer + DECODE_OPTIONS = { + max_nested: false, + create_additions: false, + }.freeze + + def self.encode(raw) + self._init! + + raw = {} if raw.nil? + + JSON.fast_generate(raw) + end + + def self.decode(raw) + self._init! + + return {} if raw.nil? || raw.empty? + JSON.parse(raw, DECODE_OPTIONS) + end + + protected + + def self._init! + return if defined?(@_init) + require "json" + @_init = true + end + end + end + end +end diff --git a/spec/unit/rails/serializers/date_serializer_spec.rb b/spec/unit/rails/serializers/date_serializer_spec.rb new file mode 100644 index 00000000..333d8efb --- /dev/null +++ b/spec/unit/rails/serializers/date_serializer_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::DateSerializer do + it 'encodes values to strings' do + expect(subject.encode(Date.new(1999, 1, 1))). to eq '1999-01-01' + end + + it 'decodes values from strings' do + expect(subject.decode('1999-12-31')). to eq Date.new(1999, 12, 31) + end +end + diff --git a/spec/unit/rails/serializers/float_serializer_spec.rb b/spec/unit/rails/serializers/float_serializer_spec.rb new file mode 100644 index 00000000..0ec2b5bc --- /dev/null +++ b/spec/unit/rails/serializers/float_serializer_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::FloatSerializer do + it 'encodes values to strings' do + expect(subject.encode(1.0)). to eq '1.0' + expect(subject.encode(42.00001)). to eq '42.00001' + expect(subject.encode(435345.40035)). to eq '435345.40035' + end + + it 'decodes values from strings' do + expect(subject.decode('1.0')). to eq 1.0 + expect(subject.decode('42')). to eq 42.0 + expect(subject.decode('435345.40035')). to eq 435345.40035 + end +end diff --git a/spec/unit/rails/serializers/integer_serializer_spec.rb b/spec/unit/rails/serializers/integer_serializer_spec.rb new file mode 100644 index 00000000..1ddab39f --- /dev/null +++ b/spec/unit/rails/serializers/integer_serializer_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::IntegerSerializer do + it 'encodes values to strings' do + expect(subject.encode(1)). to eq '1' + expect(subject.encode(42)). to eq '42' + expect(subject.encode(23425)). to eq '23425' + end + + it 'decodes values from strings' do + expect(subject.decode('1')). to eq 1 + expect(subject.decode('42')). to eq 42 + expect(subject.decode('23425')). to eq 23425 + end +end diff --git a/spec/unit/rails_spec.rb b/spec/unit/rails_spec.rb index 557d4327..4c81a1d4 100644 --- a/spec/unit/rails_spec.rb +++ b/spec/unit/rails_spec.rb @@ -1,22 +1,22 @@ -require "spec_helper" +require 'spec_helper' describe Vault::Rails do - describe ".serializer_for" do - it "accepts a string" do - serializer = Vault::Rails.serializer_for("json") - expect(serializer).to be(Vault::Rails::JSONSerializer) + describe '.serializer_for' do + it 'accepts a string' do + serializer = Vault::Rails.serializer_for('json') + expect(serializer).to be(Vault::Rails::Serializers::JSONSerializer) end - it "accepts a symbol" do + it 'accepts a symbol' do serializer = Vault::Rails.serializer_for(:json) - expect(serializer).to be(Vault::Rails::JSONSerializer) + expect(serializer).to be(Vault::Rails::Serializers::JSONSerializer) end - it "raises an exception when there is no serializer for the key" do + it 'raises an exception when there is no serializer for the key' do expect { Vault::Rails.serializer_for(:not_a_serializer) - }.to raise_error(Vault::Rails::UnknownSerializerError) { |e| - expect(e.message).to match("Unknown Vault serializer `:not_a_serializer'") + }.to raise_error(Vault::Rails::Serializers::UnknownSerializerError) { |e| + expect(e.message).to match('Unknown Vault serializer `:not_a_serializer`') } end end From f71550c0e09f3fe57a4dfdb6063236f73d5286b6 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Fri, 17 Aug 2018 10:59:43 +0300 Subject: [PATCH 009/143] Convergent encryption support What does this PR do? --------------------- * Adds support for convergent encryption Where should the reviewer start? -------------------------------- * `lib/vault/rails.rb` * `lib/vault/encrypted_model.rb` Any background context you want to provide? ------------------------------------------- * Vault supports convergent encryption since v0.6.1, but this gem does not take advantage of this functionality. --- .travis.yml | 8 +- gemfiles/rails_4.1.gemfile.lock | 8 +- gemfiles/rails_4.2.gemfile.lock | 8 +- lib/vault/encrypted_model.rb | 20 +++-- lib/vault/rails.rb | 78 ++++++++++++----- lib/vault/rails/configurable.rb | 17 ++++ spec/dummy/app/models/person.rb | 2 + ...816090008_add_email_encrypted_to_people.rb | 5 ++ spec/dummy/db/schema.rb | 4 +- spec/integration/rails_spec.rb | 86 +++++++++++++++++++ spec/unit/rails_spec.rb | 86 ++++++++++++++++++- vault.gemspec | 2 +- 12 files changed, 278 insertions(+), 46 deletions(-) create mode 100644 spec/dummy/db/migrate/20180816090008_add_email_encrypted_to_people.rb diff --git a/.travis.yml b/.travis.yml index daba7fc9..da488747 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,10 @@ dist: trusty sudo: false env: - - VAULT_VERSION=0.6.0 - - VAULT_VERSION=0.5.3 - - VAULT_VERSION=0.4.1 - - VAULT_VERSION=0.3.1 + - VAULT_VERSION=0.10.4 + - VAULT_VERSION=0.9.6 + - VAULT_VERSION=0.8.3 + - VAULT_VERSION=0.7.3 gemfile: - Gemfile diff --git a/gemfiles/rails_4.1.gemfile.lock b/gemfiles/rails_4.1.gemfile.lock index 4e6eeb78..e76ba884 100644 --- a/gemfiles/rails_4.1.gemfile.lock +++ b/gemfiles/rails_4.1.gemfile.lock @@ -3,7 +3,7 @@ PATH specs: vault-rails (0.4.0) rails (>= 4.1, < 5.1) - vault (~> 0.5) + vault (~> 0.7) GEM remote: https://rubygems.org/ @@ -39,6 +39,7 @@ GEM rake thor (>= 0.14.0) arel (5.0.1.20140414130214) + aws-sigv4 (1.0.3) builder (3.2.3) coderay (1.1.1) concurrent-ruby (1.0.5) @@ -105,7 +106,8 @@ GEM polyglot (>= 0.3.1) tzinfo (1.2.3) thread_safe (~> 0.1) - vault (0.10.1) + vault (0.12.0) + aws-sigv4 PLATFORMS ruby @@ -121,4 +123,4 @@ DEPENDENCIES vault-rails! BUNDLED WITH - 1.15.3 + 1.16.2 diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 762ac15d..992fab53 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -3,7 +3,7 @@ PATH specs: vault-rails (0.4.0) rails (>= 4.1, < 5.1) - vault (~> 0.5) + vault (~> 0.7) GEM remote: https://rubygems.org/ @@ -48,6 +48,7 @@ GEM rake thor (>= 0.14.0) arel (6.0.3) + aws-sigv4 (1.0.3) builder (3.2.3) coderay (1.1.1) concurrent-ruby (1.0.5) @@ -129,7 +130,8 @@ GEM thread_safe (0.3.6) tzinfo (1.2.3) thread_safe (~> 0.1) - vault (0.10.1) + vault (0.12.0) + aws-sigv4 PLATFORMS ruby @@ -145,4 +147,4 @@ DEPENDENCIES vault-rails! BUNDLED WITH - 1.15.3 + 1.16.2 diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 924e44e3..01459a85 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -25,6 +25,8 @@ module ClassMethods # # @option options [Symbol] :encrypted_column # the name of the encrypted column (default: +#{column}_encrypted+) + # @option options [Bool] :convergent + # use convergent encryption (default: +false+) # @option options [String] :path # the path to the transit backend (default: +transit+) # @option options [String] :key @@ -39,6 +41,7 @@ def vault_attribute(attribute, options = {}) encrypted_column = options[:encrypted_column] || "#{attribute}_encrypted" path = options[:path] || "transit" key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute}" + convergent = options.fetch(:convergent, false) # Sanity check options! _vault_validate_options!(options) @@ -111,6 +114,7 @@ def vault_attribute(attribute, options = {}) path: path, serializer: serializer, encrypted_column: encrypted_column, + convergent: convergent } self @@ -199,6 +203,7 @@ def __vault_load_attribute!(attribute, options) path = options[:path] serializer = options[:serializer] column = options[:encrypted_column] + convergent = options[:convergent] # Load the ciphertext ciphertext = read_attribute(column) @@ -210,7 +215,7 @@ def __vault_load_attribute!(attribute, options) end # Load the plaintext value - plaintext = Vault::Rails.decrypt(path, key, ciphertext) + plaintext = Vault::Rails.decrypt(path, key, ciphertext, Vault.client, convergent) # Deserialize the plaintext value, if a serializer exists if serializer @@ -252,16 +257,15 @@ def __vault_encrypt_attributes! # Encrypt a single attribute using Vault and persist back onto the # encrypted attribute value. def __vault_encrypt_attribute!(attribute, options) + # Only persist changed attributes to minimize requests - this helps + # minimize the number of requests to Vault. + return unless changed.include?("#{attribute}") + key = options[:key] path = options[:path] serializer = options[:serializer] column = options[:encrypted_column] - - # Only persist changed attributes to minimize requests - this helps - # minimize the number of requests to Vault. - if !changed.include?("#{attribute}") - return - end + convergent = options[:convergent] # Get the current value of the plaintext attribute plaintext = instance_variable_get("@#{attribute}") @@ -272,7 +276,7 @@ def __vault_encrypt_attribute!(attribute, options) end # Generate the ciphertext and store it back as an attribute - ciphertext = Vault::Rails.encrypt(path, key, plaintext) + ciphertext = Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) # Write the attribute back, so that we don't have to reload the record # to get the ciphertext diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 3a0bc47f..8227554d 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -75,22 +75,22 @@ def respond_to_missing?(m, include_private = false) # the plaintext to encrypt # @param [Vault::Client] client # the Vault client to use + # @param [Bool] convergent + # use convergent encryption # # @return [String] # the encrypted cipher text - def encrypt(path, key, plaintext, client = self.client) - if plaintext.blank? - return plaintext - end + def encrypt(path, key, plaintext, client = self.client, convergent = false) + return plaintext if plaintext.blank? path = path.to_s if !path.is_a?(String) key = key.to_s if !key.is_a?(String) with_retries do if self.enabled? - result = self.vault_encrypt(path, key, plaintext, client) + result = self.vault_encrypt(path, key, plaintext, client, convergent) else - result = self.memory_encrypt(path, key, plaintext, client) + result = self.memory_encrypt(path, key, plaintext, client, convergent) end return self.force_encoding(result) @@ -110,7 +110,7 @@ def encrypt(path, key, plaintext, client = self.client) # # @return [String] # the decrypted plaintext text - def decrypt(path, key, ciphertext, client = self.client) + def decrypt(path, key, ciphertext, client = self.client, convergent = false) if ciphertext.blank? return ciphertext end @@ -120,9 +120,9 @@ def decrypt(path, key, ciphertext, client = self.client) with_retries do if self.enabled? - result = self.vault_decrypt(path, key, ciphertext, client) + result = self.vault_decrypt(path, key, ciphertext, client, convergent) else - result = self.memory_decrypt(path, key, ciphertext, client) + result = self.memory_decrypt(path, key, ciphertext, client, convergent) end return self.force_encoding(result) @@ -149,7 +149,7 @@ def serializer_for(key) protected # Perform in-memory encryption. This is useful for testing and development. - def memory_encrypt(path, key, plaintext, client) + def memory_encrypt(path, key, plaintext, _client, convergent) log_warning(DEV_WARNING) if self.in_memory_warnings_enabled? return nil if plaintext.nil? @@ -157,11 +157,18 @@ def memory_encrypt(path, key, plaintext, client) cipher = OpenSSL::Cipher::AES.new(128, :CBC) cipher.encrypt cipher.key = memory_key_for(path, key) - return Base64.strict_encode64(cipher.update(plaintext) + cipher.final) + + iv = if convergent + cipher.iv = Vault::Rails.convergent_encryption_context.first(16) + else + cipher.random_iv + end + + Base64.strict_encode64(iv + cipher.update(plaintext) + cipher.final) end # Perform in-memory decryption. This is useful for testing and development. - def memory_decrypt(path, key, ciphertext, client) + def memory_decrypt(path, key, ciphertext, _client, convergent) log_warning(DEV_WARNING) if self.in_memory_warnings_enabled? return nil if ciphertext.nil? @@ -169,29 +176,54 @@ def memory_decrypt(path, key, ciphertext, client) cipher = OpenSSL::Cipher::AES.new(128, :CBC) cipher.decrypt cipher.key = memory_key_for(path, key) - return cipher.update(Base64.strict_decode64(ciphertext)) + cipher.final + + ciphertext_bytes = Base64.strict_decode64(ciphertext) + + cipher.iv = ciphertext_bytes.first(16) + ciphertext = ciphertext_bytes[16..-1] + + cipher.update(ciphertext) + cipher.final end # Perform encryption using Vault. This will raise exceptions if Vault is # unavailable. - def vault_encrypt(path, key, plaintext, client) + def vault_encrypt(path, key, plaintext, client, convergent) return nil if plaintext.nil? - route = File.join(path, "encrypt", key) - secret = client.logical.write(route, - plaintext: Base64.strict_encode64(plaintext), - ) - return secret.data[:ciphertext] + route = File.join(path, 'encrypt', key) + options = { + plaintext: Base64.strict_encode64(plaintext) + } + + if convergent + options.merge!( + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + convergent_encryption: true, + derived: true + ) + end + + secret = client.logical.write(route, options) + secret.data[:ciphertext] end # Perform decryption using Vault. This will raise exceptions if Vault is # unavailable. - def vault_decrypt(path, key, ciphertext, client) + def vault_decrypt(path, key, ciphertext, client, convergent) return nil if ciphertext.nil? - route = File.join(path, "decrypt", key) - secret = client.logical.write(route, ciphertext: ciphertext) - return Base64.strict_decode64(secret.data[:plaintext]) + options = { ciphertext: ciphertext } + + if convergent + options.merge!( + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context) + ) + end + + route = File.join(path, 'decrypt', key) + secret = client.logical.write(route, options) + + Base64.strict_decode64(secret.data[:plaintext]) end # The symmetric key for the given params. diff --git a/lib/vault/rails/configurable.rb b/lib/vault/rails/configurable.rb index 50408df4..79effa3d 100644 --- a/lib/vault/rails/configurable.rb +++ b/lib/vault/rails/configurable.rb @@ -119,6 +119,23 @@ def retry_max_wait def retry_max_wait=(val) @retry_max_wait = val end + + # Gets the convergent encryption context for deriving + # an enctyption key when using convergent encryption + # Raises an exception when convergent option is set + # to true and context is not privided + def convergent_encryption_context + unless @convergent_encryption_context + raise StandardError, 'Missinng configuration oprion convergent_encryption_context!' + end + + @convergent_encryption_context + end + + # Sets the convergent encryption context for use with convergent encryption + def convergent_encryption_context=(context) + @convergent_encryption_context = context + end end end end diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index c52ccae2..5cf21d43 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -21,5 +21,7 @@ class Person < ActiveRecord::Base decode: ->(raw) { raw && raw[3...-3] } vault_attribute :non_ascii + + vault_attribute :email, convergent: true end diff --git a/spec/dummy/db/migrate/20180816090008_add_email_encrypted_to_people.rb b/spec/dummy/db/migrate/20180816090008_add_email_encrypted_to_people.rb new file mode 100644 index 00000000..0a829f60 --- /dev/null +++ b/spec/dummy/db/migrate/20180816090008_add_email_encrypted_to_people.rb @@ -0,0 +1,5 @@ +class AddEmailEncryptedToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :email_encrypted, :string + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index a6fd409a..d81eda9d 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -1,4 +1,3 @@ -# encoding: UTF-8 # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. @@ -11,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150428220101) do +ActiveRecord::Schema.define(version: 20180816090008) do create_table "people", force: :cascade do |t| t.string "name" @@ -23,6 +22,7 @@ t.string "non_ascii_encrypted" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.string "email_encrypted" end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index bb5c8049..32e528c9 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -356,6 +356,37 @@ end end + context 'when convergent encryption is used' do + before :each do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + it 'generates the same ciphertext for the same plaintext' do + email = 'user@example.com' + + first_person = Person.create!(email: email) + second_person = Person.create!(email: email) + + first_person.reload + second_person.reload + + expect(first_person.email_encrypted).not_to be_blank + expect(second_person.email_encrypted).not_to be_blank + + expect(first_person.email_encrypted).to eq second_person.email_encrypted + end + + it 'generates different ciphertexts for different plaintexts' do + first_person = Person.create!(email: 'john@example.com') + second_person = Person.create!(email: 'todd@example.com') + + first_person.reload + second_person.reload + + expect(first_person.email_encrypted).not_to eq(second_person.email_encrypted) + end + end + context 'with errors' do it 'raises the appropriate exception' do expect { @@ -363,4 +394,59 @@ }.to raise_error(Vault::HTTPClientError) end end + + context "in-memory encryption" do + before(:each) do + # Force in-memory encryption + allow(Vault::Rails).to receive(:enabled?).and_return(false) + end + + context 'when convergent encryption is not used' do + it 'generates different ciphertexts for the same plaintext' do + ssn = '123-45-6789' + + first_person = Person.create!(ssn: ssn) + second_person = Person.create!(ssn: ssn) + + first_person.reload + second_person.reload + + expect(first_person.ssn).to eq ssn + expect(second_person.ssn).to eq ssn + + expect(first_person.ssn_encrypted).not_to eq(second_person.ssn_encrypted) + end + end + + context 'when convergent encryption is used' do + before :each do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + it 'generates the same ciphertext when given the same plaintext' do + email = 'knifemaker@example.com' + + first_person = Person.create!(email: email) + second_person = Person.create!(email: email) + + first_person.reload + second_person.reload + + expect(first_person.email_encrypted).not_to be_blank + expect(second_person.email_encrypted).not_to be_blank + + expect(first_person.email_encrypted).to eq(second_person.email_encrypted) + end + + it "generates different ciphertext for different plaintext" do + first_person = Person.create!(email: "medford@example.com") + second_person = Person.create!(email: "begg@example.com") + + first_person.reload + second_person.reload + + expect(first_person.email_encrypted).not_to eq(second_person.email_encrypted) + end + end + end end diff --git a/spec/unit/rails_spec.rb b/spec/unit/rails_spec.rb index 4c81a1d4..9b39f727 100644 --- a/spec/unit/rails_spec.rb +++ b/spec/unit/rails_spec.rb @@ -13,11 +13,93 @@ end it 'raises an exception when there is no serializer for the key' do - expect { + expect do Vault::Rails.serializer_for(:not_a_serializer) - }.to raise_error(Vault::Rails::Serializers::UnknownSerializerError) { |e| + end.to raise_error(Vault::Rails::Serializers::UnknownSerializerError) { |e| expect(e.message).to match('Unknown Vault serializer `:not_a_serializer`') } end end + + describe '.encrypt' do + context 'when convergent encryption is enabled' do + before do + allow(Vault::Rails).to receive(:enabled?).and_return(true) + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16) + end + + it 'sends the correct paramaters to vault client' do + expected_route = 'path/encrypt/key' + expected_options = { + plaintext: Base64.strict_encode64('plaintext'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + convergent_encryption: true, + derived: true + } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.encrypt('path', 'key', 'plaintext', Vault::Rails.client, true) + end + end + + context 'when convergent encryption is disabled' do + before do + allow(Vault::Rails).to receive(:enabled?).and_return(true) + end + + it 'sends the correct paramaters to vault client' do + expected_route = 'path/encrypt/key' + expected_options = { plaintext: Base64.strict_encode64('plaintext') } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.encrypt('path', 'key', 'plaintext', Vault::Rails.client, false) + end + end + end + + describe '.decrypt' do + context 'when convergent encryption is enabled' do + before do + allow(Vault::Rails).to receive(:enabled?).and_return(true) + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16) + end + + it 'sends the correct paramaters to vault client' do + expected_route = 'path/decrypt/key' + expected_options = { + ciphertext: 'ciphertext', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context) + } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.decrypt('path', 'key', 'ciphertext', Vault::Rails.client, true) + end + end + + context 'when convergent encryption is disabled' do + before do + allow(Vault::Rails).to receive(:enabled?).and_return(true) + end + + it 'sends the correct paramaters to vault client' do + expected_route = 'path/decrypt/key' + expected_options = { ciphertext: 'ciphertext' } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.decrypt('path', 'key', 'ciphertext', Vault::Rails.client, false) + end + end + end end diff --git a/vault.gemspec b/vault.gemspec index 768c0b00..a1790342 100644 --- a/vault.gemspec +++ b/vault.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |s| s.test_files = Dir["spec/**/*"] s.add_dependency "rails", [">= 4.1", "< 5.1"] - s.add_dependency "vault", "~> 0.5" + s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" From dc550ef05636eb7cbdce937a8a3d048d2960e093 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Mon, 8 Oct 2018 11:40:19 +0300 Subject: [PATCH 010/143] Improve lazy decrypt Only load the attributes that we need to when using vault_lazy_decrypt! --- lib/vault/encrypted_model.rb | 61 +++++++++++----------------------- spec/integration/rails_spec.rb | 35 ++++++++++++++++--- 2 files changed, 51 insertions(+), 45 deletions(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 01459a85..576cf09b 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -64,29 +64,25 @@ def vault_attribute(attribute, options = {}) # Getter define_method("#{attribute}") do - self.__vault_load_attributes! unless @__vault_loaded - instance_variable_get("@#{attribute}") + if instance_variable_defined?("@#{attribute}") + return instance_variable_get("@#{attribute}") + end + + __vault_load_attribute!(attribute, self.class.__vault_attributes[attribute]) end # Setter define_method("#{attribute}=") do |value| - self.__vault_load_attributes! unless @__vault_loaded - # We always set it as changed without comparing with the current value # because we allow our held values to be mutated, so we need to assume # that if you call attr=, you want it send back regardless. - attribute_will_change!("#{attribute}") instance_variable_set("@#{attribute}", value) - - # Return the value to be consistent with other AR methods. - value end # Checker define_method("#{attribute}?") do - self.__vault_load_attributes! unless @__vault_loaded - instance_variable_get("@#{attribute}").present? + send("#{attribute}").present? end # Dirty method @@ -154,8 +150,8 @@ def _vault_validate_options!(options) end end - def vault_lazy_decrypt - @vault_lazy_decrypt ||= false + def vault_lazy_decrypt? + !!@vault_lazy_decrypt end def vault_lazy_decrypt! @@ -166,7 +162,7 @@ def vault_lazy_decrypt! included do # After a resource has been initialized, immediately communicate with # Vault and decrypt any attributes unless vault_lazy_decrypt is set. - after_initialize :__vault_initialize_attributes! + after_initialize :__vault_load_attributes! # After we save the record, persist all the values to Vault and reload # them attributes from Vault to ensure we have the proper attributes set. @@ -178,23 +174,12 @@ def vault_lazy_decrypt! # Decrypt all the attributes from Vault. # @return [true] - def __vault_initialize_attributes! - if self.class.vault_lazy_decrypt - @__vault_loaded = false - return - end - - __vault_load_attributes! - end - def __vault_load_attributes! + return if self.class.vault_lazy_decrypt? + self.class.__vault_attributes.each do |attribute, options| self.__vault_load_attribute!(attribute, options) end - - @__vault_loaded = true - - return true end # Decrypt and load a single attribute from Vault. @@ -208,19 +193,14 @@ def __vault_load_attribute!(attribute, options) # Load the ciphertext ciphertext = read_attribute(column) - # If the user provided a value for the attribute, do not try to load - # it from Vault - if instance_variable_get("@#{attribute}") - return - end + # If the user provided a value for the attribute, do not try to load it from Vault + return if instance_variable_get("@#{attribute}") # Load the plaintext value plaintext = Vault::Rails.decrypt(path, key, ciphertext, Vault.client, convergent) # Deserialize the plaintext value, if a serializer exists - if serializer - plaintext = serializer.decode(plaintext) - end + plaintext = serializer.decode(plaintext) if serializer # Write the virtual attribute with the plaintext value instance_variable_set("@#{attribute}", plaintext) @@ -235,9 +215,7 @@ def __vault_persist_attributes! # If there are any changes to the model, update them all at once, # skipping any callbacks and validation. This is okay, because we are # already in a transaction due to the callback. - if !changes.empty? - self.update_columns(changes) - end + self.update_columns(changes) if !changes.empty? true end @@ -291,13 +269,14 @@ def __vault_encrypt_attribute!(attribute, options) # reload a record from the database. def reload(*) super.tap do - # Unset all the instance variables to force the new data to be pulled - # from Vault + # Unset all the instance variables to force the new data to be pulled from Vault self.class.__vault_attributes.each do |attribute, _| - self.instance_variable_set("@#{attribute}", nil) + if instance_variable_defined?("@#{attribute}") + self.remove_instance_variable("@#{attribute}") + end end - self.__vault_initialize_attributes! + self.__vault_load_attributes! end end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 32e528c9..d9954593 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -71,6 +71,14 @@ expect(person.ssn).to eq("") end + it "loads instance variables on initialize" do + person = Person.create!(ssn: '123-45-6789', non_ascii: 'some text') + found_person = Person.find(person.id) + + expect(found_person.instance_variable_get(:@ssn)).to eq('123-45-6789') + expect(found_person.instance_variable_get(:@non_ascii)).to eq('some text') + end + it "reloads instance variables on reload" do person = Person.create!(ssn: "123-45-6789") expect(person.instance_variable_get(:@ssn)).to eq("123-45-6789") @@ -110,12 +118,31 @@ it "does not decrypt on initialization" do person = LazyPerson.create!(ssn: "123-45-6789") - person.reload - p2 = LazyPerson.find(person.id) + found_person = LazyPerson.find(person.id) + + expect(found_person.instance_variable_get("@ssn")).to eq(nil) + expect(found_person.ssn).to eq("123-45-6789") + end + + it 'only decrypts attributes that are used' do + person = LazyPerson.create!(ssn: "123-45-6789", non_ascii: 'some text') - expect(p2.instance_variable_get("@ssn")).to eq(nil) - expect(p2.ssn).to eq("123-45-6789") + found_person = LazyPerson.find(person.id) + expect(found_person.instance_variable_get(:@ssn)).to be_nil + + found_person.ssn + + expect(found_person.instance_variable_get(:@ssn)).not_to be_nil + expect(found_person.instance_variable_get(:@non_ascii)).to be_nil + end + + it 'does not decrypt attributes on reload' do + person = LazyPerson.create!(ssn: "123-45-6789", non_ascii: 'some text') + expect(person.instance_variable_get(:@ssn)).not_to be_nil + + person.reload + expect(person.instance_variable_get(:@ssn)).to be_nil end it "tracks dirty attributes" do From d1013b6c50fa6b3ad81411bf2f3cef99c5fd2649 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 9 Oct 2018 13:20:16 +0300 Subject: [PATCH 011/143] Update README.md --- README.md | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b93c81b..52644538 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Vault Rails [![Build Status](https://secure.travis-ci.org/hashicorp/vault-rails.svg?branch=master)](http://travis-ci.org/hashicorp/vault-rails) +Vault Rails [![CircleCI](https://circleci.com/gh/FundingCircle/vault-rails/tree/master.svg?style=svg)](https://circleci.com/gh/FundingCircle/vault-rails/tree/master) =========== Vault is the official Rails plugin for interacting with [Vault](https://vaultproject.io) by HashiCorp. @@ -117,6 +117,12 @@ vault_attribute :details serialize: :json ``` +This is the list of included serializers: + * `:json` + * `:date` + * `:integer` + * `:float` + - **Note** You can view the source for the exact serialization and deserialization options, but they are intentionally not customizable and cannot be used for a full object marshal/unmarshal. For customized solutions, you can also pass a module to the `:serializer` key. This module must have the following API: @@ -153,9 +159,18 @@ vault_attribute :address, - **Note** Changing the algorithm for encoding/decoding for an existing application will probably make the application crash when attempting to retrive existing values! +### Lazy Decryption +VaultRails decrypts all the encrypted attributes in an `after_initialize` callback. Although this is useful sometimes, other times it may be unnecessary. For example you may not need all or any of the encrypted attributes. +In such cases, you can use `vault_lazy_decrypt!` in your model, and VaultRails will decrypt the attributes, one by one, only when they are needed. + Caveats ------- +### Saving encrypted attributes +By default, VaultRails will encrypt and then save the encrypted attributes in an `after_save` callback. This results in a second query to the database. If you'd like to avoid this and encrypt the attributes before the model is saved, you can use `vault_persist_before_save!` in your model, and it will encrypt the attribues in a `before_save` callback. + +-- **Note** You'll need to make sure that no other callbacks interfere with these callbacks e.g. (modify the ciphertext). + ### Mounting/Creating Keys in Vault The Vault Rails plugin does not automatically mount a backend. It is assumed the proper backend is mounted and accessible by the given token. You can mount a transit backend like this: @@ -202,6 +217,27 @@ So for the example above, the key would be: my_app_people_ssn +### Convergent Encryption +Convergent encryption is a mode where the same set of plaintext and context always result in the same ciphertext. It does this by deriving a key using a key derivation function but also by deterministically deriving a nonce. You can use this if you need to check for uniqueness, or if you need the ability to search (exact-match). + +Vault supports convergent encryption since v0.6.1. We take advantage of this functionality. + +You'll need to provide an encryption context for the key derivation function in order to use convergent encryption. +```ruby +Vault::Rails.configure do |vault| + vault.convergent_encryption_context = ENV['CONVERGENT_ENCRYPTION_CONTEXT'] +end +``` + +Then, you can tell Vault to use convergent encryption like so: +```ruby +vault_attribute :ssn, + convergent: true +``` + +- **Note** Convergent encryption significantly weakens the security that encryption provides. Use this with caution! + + ### Searching Encrypted Attributes Because each column is uniquely encrypted, it is not possible to search for a particular plain-text value. For example, if the `ssn` attribute is encrypted, From 9feedaae74e425733ec74b1ecbf8293e966a9c68 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 9 Oct 2018 14:39:44 +0300 Subject: [PATCH 012/143] Bump to v0.5.0 --- CHANGELOG.md | 9 +++++++++ gemfiles/rails_4.1.gemfile.lock | 2 +- gemfiles/rails_4.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 10b57d6b..6ad8086a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Vault Rails Changelog +## v0.5.0 (October 9, 2018) +NEW FEATURES +- Convergent Encryption +- New serializers +- Encrypting attributes on before_save + +IMPROVEMENTS +- Improved lazy decryption + ## v0.4.0 (November 9, 2017) - Update supported Ruby and Rails versions [GH-50] - Ruby diff --git a/gemfiles/rails_4.1.gemfile.lock b/gemfiles/rails_4.1.gemfile.lock index e76ba884..9bced1f2 100644 --- a/gemfiles/rails_4.1.gemfile.lock +++ b/gemfiles/rails_4.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - vault-rails (0.4.0) + vault-rails (0.5.0) rails (>= 4.1, < 5.1) vault (~> 0.7) diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 992fab53..3fc9e67a 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - vault-rails (0.4.0) + vault-rails (0.5.0) rails (>= 4.1, < 5.1) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index dfce187a..0e5a0a05 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.4.0" + VERSION = "0.5.0" end end From 11b81b2efefeab859471df375b6c8fcc31c667e8 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 10 Oct 2018 08:42:58 +0300 Subject: [PATCH 013/143] Rename gem to fc-vault-rails. --- .circleci/config.yml | 12 ++++++------ vault.gemspec => fc-vault-rails.gemspec | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) rename vault.gemspec => fc-vault-rails.gemspec (76%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 24dab78f..266d886c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,10 +17,10 @@ jobs: mv vault /usr/local/bin/ - restore_cache: keys: - - vault-rails-{{checksum "vault.gemspec" }} + - vault-rails-{{checksum "fc-vault-rails.gemspec" }} - run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle - save_cache: - key: vault-rails-{{checksum "vault.gemspec" }} + key: vault-rails-{{checksum "fc-vault-rails.gemspec" }} paths: - vendor/bundle - run: bundle exec rake app:db:create @@ -48,11 +48,11 @@ jobs: - run: name: Build gem command: | - PRE_RELEASE="$CIRCLE_BRANCH" gem build vault.gemspec + PRE_RELEASE="$CIRCLE_BRANCH" gem build fc-vault-rails.gemspec - run: name: Publish gem command: | - package=$(ls -t1 vault-rails-*.gem | head -1) + package=$(ls -t1 fc-vault-rails-*.gem | head -1) gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases publish-release: @@ -75,11 +75,11 @@ jobs: - run: name: Build gem command: | - gem build vault.gemspec + gem build fc-vault-rails.gemspec - run: name: Publish gem command: | - package=$(ls -t1 vault-rails-*.gem | head -1) + package=$(ls -t1 fc-vault-rails-*.gem | head -1) gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local workflows: diff --git a/vault.gemspec b/fc-vault-rails.gemspec similarity index 76% rename from vault.gemspec rename to fc-vault-rails.gemspec index a1790342..109efda2 100644 --- a/vault.gemspec +++ b/fc-vault-rails.gemspec @@ -5,11 +5,11 @@ require "vault/rails/version" # Describe your gem and declare its dependencies: Gem::Specification.new do |s| - s.name = "vault-rails" + s.name = "fc-vault-rails" s.version = Vault::Rails::VERSION - s.authors = ["Seth Vargo"] - s.email = ["sethvargo@gmail.com"] - s.homepage = "https://github.com/hashicorp/vault-rails" + s.authors = ["Funding Circle Engineering", "Seth Vargo"] + s.email = ["engineering+fc-vault-rails@fundingcircle.com", "sethvargo@gmail.com"] + s.homepage = "https://github.com/fundingcircle/fc-vault-rails" s.summary = "Official Vault plugin for Rails" s.description = s.summary s.license = "MPL-2.0" From 09df96057a6230a0ae22f2ac6b754e03f79a4a23 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Mon, 15 Oct 2018 11:18:36 +0300 Subject: [PATCH 014/143] Remove Rails 4.1 dependency --- .travis.yml | 5 +- Appraisals | 4 - fc-vault-rails.gemspec | 2 +- gemfiles/.bundle/config | 2 + gemfiles/rails_4.1.gemfile | 7 -- gemfiles/rails_4.1.gemfile.lock | 126 -------------------------------- gemfiles/rails_4.2.gemfile | 2 +- gemfiles/rails_4.2.gemfile.lock | 6 +- 8 files changed, 8 insertions(+), 146 deletions(-) create mode 100644 gemfiles/.bundle/config delete mode 100644 gemfiles/rails_4.1.gemfile delete mode 100644 gemfiles/rails_4.1.gemfile.lock diff --git a/.travis.yml b/.travis.yml index da488747..dcdb7f25 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ env: gemfile: - Gemfile - - gemfiles/rails_4.1.gemfile - gemfiles/rails_4.2.gemfile before_install: @@ -32,9 +31,7 @@ rvm: matrix: exclude: - # Rails 4.1 cannot build json gem dependency any longer with Ruby 2.4 - - rvm: 2.4.2 - gemfile: gemfiles/rails_4.1.gemfile + # Json 1.8.3 cannot build json gem dependency any longer with Ruby 2.4 - rvm: 2.4.2 gemfile: gemfiles/rails_4.2.gemfile diff --git a/Appraisals b/Appraisals index 7a219b0f..2e94ed52 100644 --- a/Appraisals +++ b/Appraisals @@ -1,7 +1,3 @@ -appraise "rails-4.1" do - gem "rails", "~> 4.1.0" -end - appraise "rails-4.2" do gem "rails", "~> 4.2.0" end diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 109efda2..e7cfc83b 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "rails", [">= 4.1", "< 5.1"] + s.add_dependency "rails", [">= 4.2", "< 5.1"] s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" diff --git a/gemfiles/.bundle/config b/gemfiles/.bundle/config new file mode 100644 index 00000000..c127f802 --- /dev/null +++ b/gemfiles/.bundle/config @@ -0,0 +1,2 @@ +--- +BUNDLE_RETRY: "1" diff --git a/gemfiles/rails_4.1.gemfile b/gemfiles/rails_4.1.gemfile deleted file mode 100644 index f95005cd..00000000 --- a/gemfiles/rails_4.1.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "rails", "~> 4.1.0" - -gemspec :path => "../" diff --git a/gemfiles/rails_4.1.gemfile.lock b/gemfiles/rails_4.1.gemfile.lock deleted file mode 100644 index 9bced1f2..00000000 --- a/gemfiles/rails_4.1.gemfile.lock +++ /dev/null @@ -1,126 +0,0 @@ -PATH - remote: .. - specs: - vault-rails (0.5.0) - rails (>= 4.1, < 5.1) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.1.0) - actionpack (= 4.1.0) - actionview (= 4.1.0) - mail (~> 2.5.4) - actionpack (4.1.0) - actionview (= 4.1.0) - activesupport (= 4.1.0) - rack (~> 1.5.2) - rack-test (~> 0.6.2) - actionview (4.1.0) - activesupport (= 4.1.0) - builder (~> 3.1) - erubis (~> 2.7.0) - activemodel (4.1.0) - activesupport (= 4.1.0) - builder (~> 3.1) - activerecord (4.1.0) - activemodel (= 4.1.0) - activesupport (= 4.1.0) - arel (~> 5.0.0) - activesupport (4.1.0) - i18n (~> 0.6, >= 0.6.9) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.1) - tzinfo (~> 1.1) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - arel (5.0.1.20140414130214) - aws-sigv4 (1.0.3) - builder (3.2.3) - coderay (1.1.1) - concurrent-ruby (1.0.5) - diff-lcs (1.3) - erubis (2.7.0) - i18n (0.8.6) - json (1.8.3) - mail (2.5.5) - mime-types (~> 1.16) - treetop (~> 1.4.8) - method_source (0.8.2) - mime-types (1.25.1) - minitest (5.10.3) - polyglot (0.3.5) - pry (0.10.4) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - rack (1.5.5) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.1.0) - actionmailer (= 4.1.0) - actionpack (= 4.1.0) - actionview (= 4.1.0) - activemodel (= 4.1.0) - activerecord (= 4.1.0) - activesupport (= 4.1.0) - bundler (>= 1.3.0, < 2.0) - railties (= 4.1.0) - sprockets-rails (~> 2.0) - railties (4.1.0) - actionpack (= 4.1.0) - activesupport (= 4.1.0) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (10.5.0) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.2) - rspec-support (~> 3.5.0) - rspec-expectations (3.5.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-mocks (3.5.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-support (3.5.0) - slop (3.6.0) - sprockets (3.7.1) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (2.3.3) - actionpack (>= 3.0) - activesupport (>= 3.0) - sprockets (>= 2.8, < 4.0) - sqlite3 (1.3.13) - thor (0.19.4) - thread_safe (0.3.6) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) - tzinfo (1.2.3) - thread_safe (~> 0.1) - vault (0.12.0) - aws-sigv4 - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - pry - rails (~> 4.1.0) - rake (~> 10.0) - rspec (~> 3.2) - sqlite3 - vault-rails! - -BUNDLED WITH - 1.16.2 diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index cd8b45b1..6977eb02 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -4,4 +4,4 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" -gemspec :path => "../" +gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 3fc9e67a..70bb76ef 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,8 +1,8 @@ PATH remote: .. specs: - vault-rails (0.5.0) - rails (>= 4.1, < 5.1) + fc-vault-rails (0.5.0) + rails (>= 4.2, < 5.1) vault (~> 0.7) GEM @@ -139,12 +139,12 @@ PLATFORMS DEPENDENCIES appraisal (~> 2.1) bundler + fc-vault-rails! pry rails (~> 4.2.0) rake (~> 10.0) rspec (~> 3.2) sqlite3 - vault-rails! BUNDLED WITH 1.16.2 From fd5e6047b1058e89c536f2d51af23924c124a779 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Mon, 15 Oct 2018 11:52:24 +0300 Subject: [PATCH 015/143] Replace Rails with AR --- fc-vault-rails.gemspec | 3 ++- gemfiles/rails_4.2.gemfile.lock | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index e7cfc83b..e9a7b5d2 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,11 +17,12 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "rails", [">= 4.2", "< 5.1"] + s.add_dependency "activerecord", [">= 4.2", "< 5.1"] s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" + s.add_development_dependency "rails", [">= 4.2", "< 5.1"] s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec", "~> 3.2" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 70bb76ef..ddd872f2 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (0.5.0) - rails (>= 4.2, < 5.1) + activerecord (>= 4.2, < 5.1) vault (~> 0.7) GEM From 9477caa0a5168ca084b29cd84ad493d7c9aefd92 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Mon, 15 Oct 2018 13:59:00 +0300 Subject: [PATCH 016/143] Bump to v0.6.0 --- CHANGELOG.md | 7 +++++++ gemfiles/rails_4.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad8086a..05869475 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Vault Rails Changelog +## v0.6.0 (October 15, 2018) + +NOTABLE CHANGES + +- Removed 4.1 dependency +- Change dependency from Rails to ActiveRecord + ## v0.5.0 (October 9, 2018) NEW FEATURES - Convergent Encryption diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index ddd872f2..1cb3a0ae 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.5.0) + fc-vault-rails (0.6.0) activerecord (>= 4.2, < 5.1) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 0e5a0a05..715ac649 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.5.0" + VERSION = "0.6.0" end end From 4e81a3ef9c0e74519358d95eec6bdd4378f8fdb1 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 15 Oct 2018 14:24:40 +0100 Subject: [PATCH 017/143] Don't rely on Rails for the default encoding If we've included vault-rails in an application that doesn't use rails but only uses activerecord then we won't have access to `Rails.application.config` to ask for `encoding`. We wrap access to this in some guard clauses. Ideally we'd use the encoding on the database connection for the model, but the `Vault::Rails.encrypt` and `Vault::Rails.decrypt` methods don't know about models. We could change these methods to take an optional encoding parameter that the `Vault::Rails::EncryptedModel` will pass in from the database connection. However, we'd still need to work out a default for the cases where we use these methods outside the context of a model, so being more robust in the absence of a full rails application is good enough for now. --- lib/vault/rails.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 8227554d..06a5f296 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -232,11 +232,10 @@ def memory_key_for(path, key) return Base64.strict_encode64("#{path}/#{key}".ljust(16, "x")).byteslice(0..15) end - # Forces the encoding into the default Rails encoding and returns the + # Forces the encoding into the default encoding and returns the # newly encoded string. # @return [String] def force_encoding(str) - encoding = ::Rails.application.config.encoding || DEFAULT_ENCODING str.force_encoding(encoding).encode(encoding) end @@ -265,6 +264,11 @@ def log_warning(msg) ::Rails.logger.warn { msg } end end + + def encoding + encoding = ::Rails.application.config.encoding if defined?(::Rails) + encoding || DEFAULT_ENCODING + end end end end From ddd1142ddb57494117160c1b22ff237f42612c65 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 15 Oct 2018 14:34:28 +0100 Subject: [PATCH 018/143] Use `ActiveRecord::Base.logger` instead of `Rails.logger` We only rely on activerecord now, not the full rails gem so it makes sense to use the `ActiveRecord::Base.logger` instead of attempting to use the `Rails.logger` if rails is available. We don't need to guard against `ActiveRecord::Base` being defined though, as if it's not we'll have much bigger problems than whether or not we can use the logger. --- lib/vault/rails.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 06a5f296..f87d027a 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -260,9 +260,7 @@ def with_retries(client = self.client, &block) end def log_warning(msg) - if defined?(::Rails) && ::Rails.logger != nil - ::Rails.logger.warn { msg } - end + ::ActiveRecord::Base.logger.warn { msg } if ::ActiveRecord::Base.logger end def encoding From a66c7c8350774585254602862265b8d7c7369ad9 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 15 Oct 2018 14:37:06 +0100 Subject: [PATCH 019/143] Fix typo in convergent_encryption_context getter error We also make the method and docs for it look more like the one we get from `Vault::Rails.application` when it's not set. --- lib/vault/rails/configurable.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lib/vault/rails/configurable.rb b/lib/vault/rails/configurable.rb index 79effa3d..6609472b 100644 --- a/lib/vault/rails/configurable.rb +++ b/lib/vault/rails/configurable.rb @@ -120,13 +120,16 @@ def retry_max_wait=(val) @retry_max_wait = val end - # Gets the convergent encryption context for deriving - # an enctyption key when using convergent encryption - # Raises an exception when convergent option is set - # to true and context is not privided + # The convergent encryption context for deriving an + # encryption key when using convergent encryption. + # + # @raise [RuntimeError] + # if the convergent encryption context has not been set + # + # @return [String] def convergent_encryption_context - unless @convergent_encryption_context - raise StandardError, 'Missinng configuration oprion convergent_encryption_context!' + if !defined?(@convergent_encryption_context) || @convergent_encryption_context.nil? + raise RuntimeError, "Must set `Vault::Rails.convergent_encryption_context'!" end @convergent_encryption_context From df5f9fcfc7d001d41f3dae64981055088708ccee Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Tue, 16 Oct 2018 10:53:04 +0100 Subject: [PATCH 020/143] Allow explicitly setting a default encoding value If we can't rely on there being a full Rails app to get a default encoding from, and we can't rely on the database connection for the models either then it makes sense to allow setting an encoding explicitly for Vault::Rails. In most cases this will be excessive as we will be using it in a rails app and can rely on that value, but for the few cases where we can't this makes sense. If the value is explicitly set we use that, if not we fall back to the `Rails.application.config.encoding` value and if that is not set (or not present because we're not in a rails app), we fall back to a hardcoded default of UTF-8. We've also made setting this value more robust by making sure that whatever value is set is really a valid encoding. --- lib/vault/rails.rb | 12 +------- lib/vault/rails/configurable.rb | 29 ++++++++++++++++++ spec/unit/rails/configurable_spec.rb | 46 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index f87d027a..d20fa2d3 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -24,11 +24,6 @@ module Rails float: Vault::Rails::Serializers::FloatSerializer }.freeze - # The default encoding. - # - # @return [String] - DEFAULT_ENCODING = "utf-8".freeze - # The warning string to print when running in development mode. DEV_WARNING = "[vault-rails] Using in-memory cipher - this is not secure " \ "and should never be used in production-like environments!".freeze @@ -236,7 +231,7 @@ def memory_key_for(path, key) # newly encoded string. # @return [String] def force_encoding(str) - str.force_encoding(encoding).encode(encoding) + str.force_encoding(Vault::Rails.encoding).encode(Vault::Rails.encoding) end private @@ -262,11 +257,6 @@ def with_retries(client = self.client, &block) def log_warning(msg) ::ActiveRecord::Base.logger.warn { msg } if ::ActiveRecord::Base.logger end - - def encoding - encoding = ::Rails.application.config.encoding if defined?(::Rails) - encoding || DEFAULT_ENCODING - end end end end diff --git a/lib/vault/rails/configurable.rb b/lib/vault/rails/configurable.rb index 6609472b..977a2441 100644 --- a/lib/vault/rails/configurable.rb +++ b/lib/vault/rails/configurable.rb @@ -3,6 +3,13 @@ module Rails module Configurable include Vault::Configurable + # The default encoding, used if Vault::Rails.encoding is not set, + # and we're not in a rails app so can't use the default encoding for + # the rails app (via Rails.application.config.encoding) + # + # @return [String] + DEFAULT_ENCODING = Encoding::UTF_8 + # The name of the Vault::Rails application. # # @raise [RuntimeError] @@ -139,6 +146,28 @@ def convergent_encryption_context def convergent_encryption_context=(context) @convergent_encryption_context = context end + + # The encoding to be used when decrypting values. Defaults to + # UTF-8 if not explicitly set. + # + # @return [String] + def encoding + @encoding ||= default_encoding + end + + # Set the encoding to be used when decrypting values. + # + # @param [String] new_encoding + def encoding=(new_encoding) + @encoding = Encoding.find(new_encoding) + end + + private + + def default_encoding + default_encoding = ::Rails.application.config.encoding if defined?(::Rails) + Encoding.find(default_encoding || DEFAULT_ENCODING) + end end end end diff --git a/spec/unit/rails/configurable_spec.rb b/spec/unit/rails/configurable_spec.rb index c7a8a90f..90e7c839 100644 --- a/spec/unit/rails/configurable_spec.rb +++ b/spec/unit/rails/configurable_spec.rb @@ -40,4 +40,50 @@ end end end + + describe '.encoding' do + context 'when not set explicitly' do + context 'but there is a rails encoding setting' do + it 'returns that value' do + allow(::Rails.application.config).to receive(:encoding).and_return('ISO-8859-1') + expect(subject.encoding).to eq(Encoding::ISO_8859_1) + end + + it 'raises an exception if that value is not a valid encoding' do + allow(::Rails.application.config).to receive(:encoding).and_return('LINEAR_B') + expect { subject.encoding }.to raise_exception(ArgumentError, /unknown encoding name - LINEAR_B/) + end + end + + context 'and there is no rails encoding setting' do + it 'returns UTF-8' do + allow(::Rails.application.config).to receive(:encoding).and_return(nil) + expect(subject.encoding).to eq(Encoding::UTF_8) + end + end + + context 'and the gem is not in a rails app' do + it 'returns UTF-8' do + hide_const('Rails') + expect(subject.encoding).to eq(Encoding::UTF_8) + end + end + end + + context 'when configured' do + it 'returns the configured value' do + subject.configure do |vault| + vault.encoding = 'ISO-8859-1' + end + + expect(subject.encoding).to eq(Encoding::ISO_8859_1) + end + end + end + + context '.encoding=' do + it 'raises an exception if the supplied value is not a valid encoding' do + expect { subject.encoding = 'LINEAR_B' }.to raise_exception(ArgumentError, /unknown encoding name - LINEAR_B/) + end + end end From e4135e9b1f6980e1a1a055cb0d4648ad06adfc07 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 16 Oct 2018 16:17:05 +0300 Subject: [PATCH 021/143] Allow nil values in JSON serializer. --- .../rails/serializers/json_serializer.rb | 19 ++++--------------- spec/integration/rails_spec.rb | 11 ++++++++--- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/vault/rails/serializers/json_serializer.rb b/lib/vault/rails/serializers/json_serializer.rb index f85337b3..aabd57b9 100644 --- a/lib/vault/rails/serializers/json_serializer.rb +++ b/lib/vault/rails/serializers/json_serializer.rb @@ -1,3 +1,5 @@ +require "json" + module Vault module Rails module Serializers @@ -8,27 +10,14 @@ module JSONSerializer }.freeze def self.encode(raw) - self._init! - - raw = {} if raw.nil? - + return if raw.nil? JSON.fast_generate(raw) end def self.decode(raw) - self._init! - - return {} if raw.nil? || raw.empty? + return if raw.nil? JSON.parse(raw, DECODE_OPTIONS) end - - protected - - def self._init! - return if defined?(@_init) - require "json" - @_init = true - end end end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index d9954593..8d72ef64 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -316,13 +316,18 @@ Vault::Rails.logical.write("transit/keys/dummy_people_details") end - it "has a default value for unpersisted records" do + it "allows nil for unpersisted records" do person = Person.new - expect(person.details).to eq({}) + expect(person.details).to be_nil end - it "has a default value for persisted records" do + it "allows nil for persisted records" do person = Person.create! + expect(person.details).to be_nil + end + + it 'saves an empty hash' do + person = Person.create!(details: {}) expect(person.details).to eq({}) end From dcf526be5965d9374b1ff2e4e61aafc3ed01a8c6 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Tue, 16 Oct 2018 14:28:48 +0100 Subject: [PATCH 022/143] Bump to version 0.6.1 We need to create a git tag to do the actual release. --- CHANGELOG.md | 10 ++++++++++ gemfiles/rails_4.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05869475..484f2c68 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Vault Rails Changelog +## v0.6.1 (October 16, 2018) + +NEW FEATURES +- Allow specifying encoding for decrypted values via `Vault::Rails.encoding` + +BUG FIXES +- Stop relying on Rails for default encoding of decrypted values +- Use `ActiveRecord::Base.logger` instead of `Rails.logger` +- When serialising JSON values pass through nil values as nil, not `{}` + ## v0.6.0 (October 15, 2018) NOTABLE CHANGES diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 1cb3a0ae..492442f5 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.6.0) + fc-vault-rails (0.6.1) activerecord (>= 4.2, < 5.1) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 715ac649..c485dd3f 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.6.0" + VERSION = "0.6.1" end end From 93889d5d9ffc4a481b7082d3c685e2a210aed71b Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 17 Oct 2018 08:51:47 +0300 Subject: [PATCH 023/143] Bump lowest supported version of ActiveRecord and Rails. --- Appraisals | 2 +- fc-vault-rails.gemspec | 4 +- gemfiles/rails_4.2.gemfile | 2 +- gemfiles/rails_4.2.gemfile.lock | 113 ++++++++++++++++---------------- 4 files changed, 59 insertions(+), 62 deletions(-) diff --git a/Appraisals b/Appraisals index 2e94ed52..72063733 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,3 @@ appraise "rails-4.2" do - gem "rails", "~> 4.2.0" + gem "rails", "~> 4.2.8" end diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index e9a7b5d2..698020d2 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", [">= 4.2", "< 5.1"] + s.add_dependency "activerecord", [">= 4.2.8", "< 5.1"] s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", [">= 4.2", "< 5.1"] + s.add_development_dependency "rails", [">= 4.2.8", "< 5.1"] s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec", "~> 3.2" diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 6977eb02..056f1294 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -2,6 +2,6 @@ source "https://rubygems.org" -gem "rails", "~> 4.2.0" +gem "rails", "~> 4.2.8" gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 492442f5..b918e02d 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -2,44 +2,43 @@ PATH remote: .. specs: fc-vault-rails (0.6.1) - activerecord (>= 4.2, < 5.1) + activerecord (>= 4.2.8, < 5.1) vault (~> 0.7) GEM remote: https://rubygems.org/ specs: - actionmailer (4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) + actionmailer (4.2.10) + actionpack (= 4.2.10) + actionview (= 4.2.10) + activejob (= 4.2.10) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.7.1) - actionview (= 4.2.7.1) - activesupport (= 4.2.7.1) + actionpack (4.2.10) + actionview (= 4.2.10) + activesupport (= 4.2.10) rack (~> 1.6) rack-test (~> 0.6.2) rails-dom-testing (~> 1.0, >= 1.0.5) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.7.1) - activesupport (= 4.2.7.1) + actionview (4.2.10) + activesupport (= 4.2.10) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.7.1) - activesupport (= 4.2.7.1) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (4.2.10) + activesupport (= 4.2.10) globalid (>= 0.3.0) - activemodel (4.2.7.1) - activesupport (= 4.2.7.1) + activemodel (4.2.10) + activesupport (= 4.2.10) builder (~> 3.1) - activerecord (4.2.7.1) - activemodel (= 4.2.7.1) - activesupport (= 4.2.7.1) + activerecord (4.2.10) + activemodel (= 4.2.10) + activesupport (= 4.2.10) arel (~> 6.0) - activesupport (4.2.7.1) + activesupport (4.2.10) i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) @@ -47,60 +46,58 @@ GEM bundler rake thor (>= 0.14.0) - arel (6.0.3) + arel (6.0.4) aws-sigv4 (1.0.3) builder (3.2.3) coderay (1.1.1) concurrent-ruby (1.0.5) + crass (1.0.4) diff-lcs (1.3) erubis (2.7.0) - globalid (0.4.0) + globalid (0.4.1) activesupport (>= 4.2.0) - i18n (0.8.6) - json (1.8.3) - loofah (2.0.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + loofah (2.2.2) + crass (~> 1.0.2) nokogiri (>= 1.5.9) - mail (2.6.6) - mime-types (>= 1.16, < 4) + mail (2.7.1) + mini_mime (>= 0.1.1) method_source (0.8.2) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_portile2 (2.1.0) - minitest (5.10.3) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - pkg-config (1.1.7) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) pry (0.10.4) coderay (~> 1.1.0) method_source (~> 0.8.1) slop (~> 3.4) - rack (1.6.4) + rack (1.6.10) rack-test (0.6.3) rack (>= 1.0) - rails (4.2.7.1) - actionmailer (= 4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - activemodel (= 4.2.7.1) - activerecord (= 4.2.7.1) - activesupport (= 4.2.7.1) + rails (4.2.10) + actionmailer (= 4.2.10) + actionpack (= 4.2.10) + actionview (= 4.2.10) + activejob (= 4.2.10) + activemodel (= 4.2.10) + activerecord (= 4.2.10) + activesupport (= 4.2.10) bundler (>= 1.3.0, < 2.0) - railties (= 4.2.7.1) + railties (= 4.2.10) sprockets-rails rails-deprecated_sanitizer (1.0.3) activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) - activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) + rails-dom-testing (1.0.9) + activesupport (>= 4.2.0, < 5.0) + nokogiri (~> 1.6) rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (4.2.7.1) - actionpack (= 4.2.7.1) - activesupport (= 4.2.7.1) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (4.2.10) + actionpack (= 4.2.10) + activesupport (= 4.2.10) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rake (10.5.0) @@ -118,17 +115,17 @@ GEM rspec-support (~> 3.5.0) rspec-support (3.5.0) slop (3.6.0) - sprockets (3.7.1) + sprockets (3.7.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.0) + sprockets-rails (3.2.1) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) sqlite3 (1.3.13) thor (0.19.4) thread_safe (0.3.6) - tzinfo (1.2.3) + tzinfo (1.2.5) thread_safe (~> 0.1) vault (0.12.0) aws-sigv4 @@ -141,7 +138,7 @@ DEPENDENCIES bundler fc-vault-rails! pry - rails (~> 4.2.0) + rails (~> 4.2.8) rake (~> 10.0) rspec (~> 3.2) sqlite3 From 7dd889ebf3670ee5586b108145645343abad292d Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 23 Oct 2018 19:54:45 +0300 Subject: [PATCH 024/143] Improve CircleCI integration * Update CircelCI config * Use a docker image with RVM for CircleCI * Use WWTD for running tests with multiple Ruby/Rails versions --- .circleci/config.yml | 94 ++++++++++++++++++++++++--------- .travis.yml | 17 +++--- fc-vault-rails.gemspec | 1 + gemfiles/rails_4.2.gemfile.lock | 2 + 4 files changed, 80 insertions(+), 34 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 266d886c..e07404ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,83 +1,127 @@ version: 2 + +defaults: &defaults + docker: + - image: quay.io/fundingcircle/centos-ruby:ci-rvm + auth: + username: $DOCKER_USERNAME + password: $DOCKER_PASSWORD + environment: + RACK_ENV: test + VAULT_VERSION: 0.10.4 + jobs: tests: - docker: - - image: ruby:2.4.4-alpine - environment: - RACK_ENV: test - VAULT_VERSION: 0.10.0 + <<: *defaults + steps: - checkout - - run: apk add --no-cache build-base sqlite-dev tzdata + - run: yum -y install wget unzip sqlite + - run: yum clean all + - run: name: Install Vault command: | wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip unzip vault.zip mv vault /usr/local/bin/ + - restore_cache: keys: - - vault-rails-{{checksum "fc-vault-rails.gemspec" }} - - run: bundle check --path=vendor/bundle || bundle install --path=vendor/bundle + - vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum ".travis.yml" }} + + - run: + shell: /bin/bash -l + command: | + rvm use --default 2.5.1 + bundle check --path=/usr/local/rvm/gems || bundle install --path=/usr/local/rvm/gems + + - run: + shell: /bin/bash -l + command: | + bundle exec rake app:db:create + bundle exec rake app:db:schema:load + bundle exec rake app:db:test:prepare + bundle exec wwtd + - save_cache: - key: vault-rails-{{checksum "fc-vault-rails.gemspec" }} + key: vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum ".travis.yml" }} paths: - - vendor/bundle - - run: bundle exec rake app:db:create - - run: bundle exec rake app:db:schema:load - - run: bundle exec rake app:db:test:prepare - - run: bundle exec rspec + - /usr/local/rvm/gems publish-pre-release: - docker: - - image: ruby:2.4.4-alpine + <<: *defaults + steps: - checkout - - run: - name: Install cURL - command: apk add --no-cache curl + - run: yum -y install curl + - run: yum clean all + - run: name: Login to JFrog command: | mkdir -p ~/.gem curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials chmod 600 ~/.gem/credentials + + - run: + shell: /bin/bash -l + command: | + rvm use --default 2.5.1 + - run: name: Install Gem Versioner + shell: /bin/bash -l command: gem install gem-versioner --version '~> 1.0' --no-document + - run: name: Build gem + shell: /bin/bash -l command: | PRE_RELEASE="$CIRCLE_BRANCH" gem build fc-vault-rails.gemspec + - run: name: Publish gem + shell: /bin/bash -l command: | package=$(ls -t1 fc-vault-rails-*.gem | head -1) gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases publish-release: - docker: - - image: ruby:2.4.4-alpine + <<: *defaults + steps: + - checkout - - run: - name: Install cURL - command: apk add --no-cache curl git + - run: yum -y install curl + - run: yum clean all + - run: name: Login to JFrog command: | mkdir -p ~/.gem curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials chmod 600 ~/.gem/credentials + + - run: + shell: /bin/bash -l + command: | + rvm use --default 2.5.1 + - run: name: Install Gem Versioner + shell: /bin/bash -l command: gem install gem-versioner --version '~> 1.0' --no-document + - run: name: Build gem + shell: /bin/bash -l command: | gem build fc-vault-rails.gemspec + - run: name: Publish gem + shell: /bin/bash -l command: | package=$(ls -t1 fc-vault-rails-*.gem | head -1) gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local @@ -88,9 +132,11 @@ workflows: test-and-release: jobs: - tests: + context: org-global filters: tags: only: /.*/ + - publish-pre-release: context: org-global filters: diff --git a/.travis.yml b/.travis.yml index dcdb7f25..bbc447ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,6 @@ cache: bundler dist: trusty sudo: false -env: - - VAULT_VERSION=0.10.4 - - VAULT_VERSION=0.9.6 - - VAULT_VERSION=0.8.3 - - VAULT_VERSION=0.7.3 - gemfile: - Gemfile - gemfiles/rails_4.2.gemfile @@ -25,14 +19,17 @@ branches: - master rvm: - - 2.2.8 - - 2.3.5 - - 2.4.2 + - 2.2.10 + - 2.3.7 + - 2.4.4 + - 2.5.1 matrix: exclude: # Json 1.8.3 cannot build json gem dependency any longer with Ruby 2.4 - - rvm: 2.4.2 + - rvm: 2.4.4 + gemfile: gemfiles/rails_4.2.gemfile + - rvm: 2.5.1 gemfile: gemfiles/rails_4.2.gemfile before_script: diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 698020d2..30674434 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec", "~> 3.2" s.add_development_dependency "sqlite3" + s.add_development_dependency "wwtd" end diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index b918e02d..f907d4d7 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -129,6 +129,7 @@ GEM thread_safe (~> 0.1) vault (0.12.0) aws-sigv4 + wwtd (1.3.0) PLATFORMS ruby @@ -142,6 +143,7 @@ DEPENDENCIES rake (~> 10.0) rspec (~> 3.2) sqlite3 + wwtd BUNDLED WITH 1.16.2 From bb058681013a8b5dd244ad4fb685e47bd23545da Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 16 Oct 2018 08:36:02 +0300 Subject: [PATCH 025/143] Attribute API Use Attribute API instead of instance variables for encrypted attributes. --- README.md | 25 ++++ fc-vault-rails.gemspec | 1 + gemfiles/rails_4.2.gemfile.lock | 2 + lib/vault/encrypted_model.rb | 75 +++++------- spec/dummy/app/models/eager_person.rb | 30 +++++ spec/dummy/app/models/person.rb | 14 ++- spec/dummy/config/initializers/vault.rb | 1 + ...00_add_integer_data_encrypted_to_people.rb | 5 + ...3700_add_float_data_encrypted_to_people.rb | 5 + ...43900_add_time_data_encrypted_to_people.rb | 5 + spec/dummy/db/schema.rb | 9 +- spec/integration/rails_spec.rb | 56 +++++---- spec/spec_helper.rb | 5 + spec/unit/encrypted_model_spec.rb | 110 +++++++++++++----- 14 files changed, 238 insertions(+), 105 deletions(-) create mode 100644 spec/dummy/app/models/eager_person.rb create mode 100644 spec/dummy/db/migrate/20181016143500_add_integer_data_encrypted_to_people.rb create mode 100644 spec/dummy/db/migrate/20181016143700_add_float_data_encrypted_to_people.rb create mode 100644 spec/dummy/db/migrate/20181016143900_add_time_data_encrypted_to_people.rb diff --git a/README.md b/README.md index 52644538..83e7b728 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,31 @@ vault_attribute :credit_card, - **Note** Changing this value for an existing application will make existing values no longer decryptable! +#### Attribute types +The latest version of VaultRails uses ActiveRecord's Attribute API to implement encrypted attributes. This allows us to use its's internal type casting mechanism, and makes encrypted attributes behave like normal ActiveRecord ones. If you don't specify a type, the default is ActiveRecord::Type::Value, which can hold any value. + +Since Vault ciphertexts are always Base64 encoded strings, we still need to tell ActiveRecord how to handle this. ActiveRecord knows how to convert between ruby objects and datatypes that the database understands, but this is not useful to us in this case. There are a number of ways to deal with this: + * use serializers + * use `encode`/`decode` procs + * Define your own types (inherit from `ActiveRecord::Type::Value`) and override the `type_cast_from_database`/`type_cast_for_database` (AR 4.2) or `serialize`/`deserialize` (AR 5+) + +```ruby +class User < ActiveRecord::Base + include Vault::EncryptedModel + vault_attribute :date_of_birth, + type: :date, + encode: -> (raw) { raw.to_s if raw }, + decode: -> (raw) { raw.to_date if raw } +end + +>> user = User.new +=> # +>> user.date_of_birth = '1988-10-15' +=> "1988-10-15" +>> user.date_of_birth.class +=> Date +``` + #### Automatic serializing By default, all values are assumed to be "text" fields in the database. Sometimes it is beneficial for your application to work with a more flexible data structure (such as a Hash or Array). Vault-rails can automatically serialize and deserialize these structures for you: diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 30674434..12b59e9d 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -23,6 +23,7 @@ Gem::Specification.new do |s| s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" s.add_development_dependency "rails", [">= 4.2.8", "< 5.1"] + s.add_development_dependency "byebug" s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec", "~> 3.2" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index f907d4d7..d2a14c82 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -49,6 +49,7 @@ GEM arel (6.0.4) aws-sigv4 (1.0.3) builder (3.2.3) + byebug (10.0.2) coderay (1.1.1) concurrent-ruby (1.0.5) crass (1.0.4) @@ -137,6 +138,7 @@ PLATFORMS DEPENDENCIES appraisal (~> 2.1) bundler + byebug fc-vault-rails! pry rails (~> 4.2.8) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 576cf09b..cdbbd16b 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -37,10 +37,10 @@ module ClassMethods # a proc to encode the value with # @option options [Proc] :decode # a proc to decode the value with - def vault_attribute(attribute, options = {}) - encrypted_column = options[:encrypted_column] || "#{attribute}_encrypted" + def vault_attribute(attribute_name, options = {}) + encrypted_column = options[:encrypted_column] || "#{attribute_name}_encrypted" path = options[:path] || "transit" - key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute}" + key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute_name}" convergent = options.fetch(:convergent, false) # Sanity check options! @@ -62,50 +62,35 @@ def vault_attribute(attribute, options = {}) serializer.define_singleton_method(:decode, &options[:decode]) end - # Getter - define_method("#{attribute}") do - if instance_variable_defined?("@#{attribute}") - return instance_variable_get("@#{attribute}") - end + attribute_type = options.fetch(:type, :value) - __vault_load_attribute!(attribute, self.class.__vault_attributes[attribute]) - end + if attribute_type.is_a?(Symbol) + constant_name = attribute_type.to_s.camelize - # Setter - define_method("#{attribute}=") do |value| - # We always set it as changed without comparing with the current value - # because we allow our held values to be mutated, so we need to assume - # that if you call attr=, you want it send back regardless. - attribute_will_change!("#{attribute}") - instance_variable_set("@#{attribute}", value) - end + unless ActiveRecord::Type.const_defined?(constant_name) + raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" + end - # Checker - define_method("#{attribute}?") do - send("#{attribute}").present? + attribute_type = ActiveRecord::Type.const_get(constant_name).new end - # Dirty method - define_method("#{attribute}_change") do - changes["#{attribute}"] - end + # Attribute API + attribute(attribute_name, attribute_type) - # Dirty method - define_method("#{attribute}_changed?") do - changed.include?("#{attribute}") + # Getter + define_method(attribute_name) do + read_attribute(attribute_name) || __vault_load_attribute!(attribute_name, self.class.__vault_attributes[attribute_name]) end - # Dirty method - define_method("#{attribute}_was") do - if changes["#{attribute}"] - changes["#{attribute}"][0] - else - public_send("#{attribute}") - end + # Setter + define_method("#{attribute_name}=") do |value| + # Force the update of the attribute, to be consistent with old behaviour + attribute_will_change!(attribute_name) + write_attribute(attribute_name, value) end # Make a note of this attribute so we can use it in the future (maybe). - __vault_attributes[attribute.to_sym] = { + __vault_attributes[attribute_name.to_sym] = { key: key, path: path, serializer: serializer, @@ -173,7 +158,6 @@ def vault_lazy_decrypt! after_save :__vault_persist_attributes! # Decrypt all the attributes from Vault. - # @return [true] def __vault_load_attributes! return if self.class.vault_lazy_decrypt? @@ -184,6 +168,9 @@ def __vault_load_attributes! # Decrypt and load a single attribute from Vault. def __vault_load_attribute!(attribute, options) + # If the user provided a value for the attribute, do not try to load it from Vault + return unless read_attribute(attribute).nil? + key = options[:key] path = options[:path] serializer = options[:serializer] @@ -193,9 +180,6 @@ def __vault_load_attribute!(attribute, options) # Load the ciphertext ciphertext = read_attribute(column) - # If the user provided a value for the attribute, do not try to load it from Vault - return if instance_variable_get("@#{attribute}") - # Load the plaintext value plaintext = Vault::Rails.decrypt(path, key, ciphertext, Vault.client, convergent) @@ -203,7 +187,7 @@ def __vault_load_attribute!(attribute, options) plaintext = serializer.decode(plaintext) if serializer # Write the virtual attribute with the plaintext value - instance_variable_set("@#{attribute}", plaintext) + write_attribute(attribute, plaintext) end # Encrypt all the attributes using Vault and set the encrypted values back @@ -246,7 +230,7 @@ def __vault_encrypt_attribute!(attribute, options) convergent = options[:convergent] # Get the current value of the plaintext attribute - plaintext = instance_variable_get("@#{attribute}") + plaintext = read_attribute(attribute) # Apply the serialize to the plaintext value, if one exists if serializer @@ -269,14 +253,13 @@ def __vault_encrypt_attribute!(attribute, options) # reload a record from the database. def reload(*) super.tap do - # Unset all the instance variables to force the new data to be pulled from Vault + # Unset all attributes to force the new data to be pulled from Vault self.class.__vault_attributes.each do |attribute, _| - if instance_variable_defined?("@#{attribute}") - self.remove_instance_variable("@#{attribute}") - end + write_attribute(attribute, nil) end self.__vault_load_attributes! + clear_changes_information end end end diff --git a/spec/dummy/app/models/eager_person.rb b/spec/dummy/app/models/eager_person.rb new file mode 100644 index 00000000..2cb5b450 --- /dev/null +++ b/spec/dummy/app/models/eager_person.rb @@ -0,0 +1,30 @@ +require "binary_serializer" + +class EagerPerson < ActiveRecord::Base + include Vault::EncryptedModel + + self.table_name = "people" + + vault_persist_before_save! + + vault_attribute :ssn + + vault_attribute :credit_card, + encrypted_column: :cc_encrypted, + path: "credit-secrets", + key: "people_credit_cards" + + vault_attribute :details, + serialize: :json + + vault_attribute :business_card, + serialize: BinarySerializer + + vault_attribute :favorite_color, + encode: ->(raw) { "xxx#{raw}xxx" }, + decode: ->(raw) { raw && raw[3...-3] } + + vault_attribute :non_ascii + + vault_attribute :email, convergent: true +end diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index 5cf21d43..d53e9a15 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -23,5 +23,17 @@ class Person < ActiveRecord::Base vault_attribute :non_ascii vault_attribute :email, convergent: true -end + vault_attribute :integer_data, + type: :integer, + serialize: :integer + + vault_attribute :float_data, + type: :float, + serialize: :float + + vault_attribute :time_data, + type: ActiveRecord::Type::Time.new, + encode: -> (raw) { raw.to_s if raw }, + decode: -> (raw) { raw.to_time if raw } +end diff --git a/spec/dummy/config/initializers/vault.rb b/spec/dummy/config/initializers/vault.rb index 69ee1e46..2f4b66bb 100644 --- a/spec/dummy/config/initializers/vault.rb +++ b/spec/dummy/config/initializers/vault.rb @@ -4,6 +4,7 @@ Vault::Rails.configure do |vault| vault.application = "dummy" + vault.address = RSpec::VaultServer.address vault.token = RSpec::VaultServer.token vault.enabled = true diff --git a/spec/dummy/db/migrate/20181016143500_add_integer_data_encrypted_to_people.rb b/spec/dummy/db/migrate/20181016143500_add_integer_data_encrypted_to_people.rb new file mode 100644 index 00000000..b4728c77 --- /dev/null +++ b/spec/dummy/db/migrate/20181016143500_add_integer_data_encrypted_to_people.rb @@ -0,0 +1,5 @@ +class AddIntegerDataEncryptedToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :integer_data_encrypted, :string + end +end diff --git a/spec/dummy/db/migrate/20181016143700_add_float_data_encrypted_to_people.rb b/spec/dummy/db/migrate/20181016143700_add_float_data_encrypted_to_people.rb new file mode 100644 index 00000000..bfbe79db --- /dev/null +++ b/spec/dummy/db/migrate/20181016143700_add_float_data_encrypted_to_people.rb @@ -0,0 +1,5 @@ +class AddFloatDataEncryptedToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :float_data_encrypted, :string + end +end diff --git a/spec/dummy/db/migrate/20181016143900_add_time_data_encrypted_to_people.rb b/spec/dummy/db/migrate/20181016143900_add_time_data_encrypted_to_people.rb new file mode 100644 index 00000000..013aeb57 --- /dev/null +++ b/spec/dummy/db/migrate/20181016143900_add_time_data_encrypted_to_people.rb @@ -0,0 +1,5 @@ +class AddTimeDataEncryptedToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :time_data_encrypted, :string + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index d81eda9d..54e034cb 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180816090008) do +ActiveRecord::Schema.define(version: 20181016143900) do create_table "people", force: :cascade do |t| t.string "name" @@ -23,6 +23,13 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "email_encrypted" + t.string "address" + t.string "address_encrypted" + t.date "date_of_birth" + t.string "date_of_birth_encrypted" + t.string "integer_data_encrypted" + t.string "float_data_encrypted" + t.string "time_data_encrypted" end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 8d72ef64..69ab59d5 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -3,10 +3,6 @@ require "spec_helper" describe Vault::Rails do - before(:all) do - Vault::Rails.sys.mount("transit", :transit) - end - context "with default options" do before(:all) do Vault::Rails.logical.write("transit/keys/dummy_people_ssn") @@ -71,21 +67,31 @@ expect(person.ssn).to eq("") end - it "loads instance variables on initialize" do + it "loads attributes on initialize" do person = Person.create!(ssn: '123-45-6789', non_ascii: 'some text') - found_person = Person.find(person.id) - expect(found_person.instance_variable_get(:@ssn)).to eq('123-45-6789') - expect(found_person.instance_variable_get(:@non_ascii)).to eq('some text') + expect_any_instance_of(Person).to receive(:__vault_load_attributes!).once.and_call_original + Person.__vault_attributes.keys.each do |attr| + expect_any_instance_of(Person).to receive(:__vault_load_attribute!).with(attr, any_args).once + end + + Person.find(person.id) end - it "reloads instance variables on reload" do + it "reloads attributes on reload" do person = Person.create!(ssn: "123-45-6789") - expect(person.instance_variable_get(:@ssn)).to eq("123-45-6789") + expect(person.ssn).to eq("123-45-6789") person.ssn = "111-11-1111" + + expect(person).to receive(:__vault_load_attributes!).once.and_call_original + Person.__vault_attributes.keys.each do |attr| + expect(person).to receive(:__vault_load_attribute!).with(attr, any_args).once.and_call_original + end + person.reload - expect(person.instance_variable_get(:@ssn)).to eq("123-45-6789") + + expect(person.ssn).to eq("123-45-6789") end it "does not try to encrypt unchanged attributes" do @@ -117,32 +123,31 @@ end it "does not decrypt on initialization" do - person = LazyPerson.create!(ssn: "123-45-6789") + lazy_person = LazyPerson.create!(ssn: "123-45-6789") - found_person = LazyPerson.find(person.id) + expect_any_instance_of(LazyPerson).not_to receive(:__vault_load_attribute!) - expect(found_person.instance_variable_get("@ssn")).to eq(nil) - expect(found_person.ssn).to eq("123-45-6789") + LazyPerson.find(lazy_person.id) end it 'only decrypts attributes that are used' do person = LazyPerson.create!(ssn: "123-45-6789", non_ascii: 'some text') found_person = LazyPerson.find(person.id) - expect(found_person.instance_variable_get(:@ssn)).to be_nil + expect(found_person).to receive(:__vault_load_attribute!).with(:ssn, any_args).once found_person.ssn - - expect(found_person.instance_variable_get(:@ssn)).not_to be_nil - expect(found_person.instance_variable_get(:@non_ascii)).to be_nil end it 'does not decrypt attributes on reload' do person = LazyPerson.create!(ssn: "123-45-6789", non_ascii: 'some text') - expect(person.instance_variable_get(:@ssn)).not_to be_nil + expect(person.ssn).not_to be_nil + + expect(person).to receive(:__vault_load_attributes!).once + expect(person).not_to receive(:__vault_load_attribute!) person.reload - expect(person.instance_variable_get(:@ssn)).to be_nil + expect(person.read_attribute(:ssn)).to be nil end it "tracks dirty attributes" do @@ -190,11 +195,14 @@ expect(person.ssn).to eq("") end - it "reloads instance variables on reload" do + it "resets attributes on reload" do person = LazyPerson.create!(ssn: "123-45-6789") - expect(person.instance_variable_get(:@ssn)).to eq("123-45-6789") + expect(person.ssn).to eq("123-45-6789") person.ssn = "111-11-1111" + + expect(person).to receive(:__vault_load_attributes!).once + person.reload expect(person.ssn).to eq("123-45-6789") @@ -211,7 +219,6 @@ context "with custom options" do before(:all) do - Vault::Rails.sys.mount("credit-secrets", :transit) Vault::Rails.logical.write("credit-secrets/keys/people_credit_cards") end @@ -262,7 +269,6 @@ context "with non-ASCII characters" do before(:all) do - Vault::Rails.sys.mount("non-ascii", :transit) Vault::Rails.logical.write("non-ascii/keys/people_non_ascii") end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 4dcf41fc..2f9dfdaf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -22,3 +22,8 @@ end require File.expand_path("../dummy/config/environment.rb", __FILE__) + +# Mount the engines we need for testing +Vault::Rails.sys.mount("transit", :transit) +Vault::Rails.sys.mount("non-ascii", :transit) +Vault::Rails.sys.mount("credit-secrets", :transit) diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 09d54ac9..2ae2465e 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -1,65 +1,96 @@ require "spec_helper" describe Vault::EncryptedModel do - let(:klass) do - Class.new(ActiveRecord::Base) do - include Vault::EncryptedModel - end - end - describe ".vault_attribute" do + let(:person) { Person.new } + it "raises an exception if a serializer and :encode is given" do expect { - klass.vault_attribute(:foo, serializer: :json, encode: ->(r) { r }) + Person.vault_attribute(:foo, serializer: :json, encode: ->(r) { r }) }.to raise_error(Vault::Rails::ValidationFailedError) end it "raises an exception if a serializer and :decode is given" do expect { - klass.vault_attribute(:foo, serializer: :json, decode: ->(r) { r }) + Person.vault_attribute(:foo, serializer: :json, decode: ->(r) { r }) }.to raise_error(Vault::Rails::ValidationFailedError) end it "defines a getter" do - klass.vault_attribute(:foo) - expect(klass.instance_methods).to include(:foo) + expect(person).to respond_to(:ssn) end it "defines a setter" do - klass.vault_attribute(:foo) - expect(klass.instance_methods).to include(:foo=) + expect(person).to respond_to(:ssn=) end it "defines a checker" do - klass.vault_attribute(:foo) - expect(klass.instance_methods).to include(:foo?) + expect(person).to respond_to(:ssn?) end it "defines dirty attribute methods" do - klass.vault_attribute(:foo) - expect(klass.instance_methods).to include(:foo_change) - expect(klass.instance_methods).to include(:foo_changed?) - expect(klass.instance_methods).to include(:foo_was) + expect(person).to respond_to(:ssn_change) + expect(person).to respond_to(:ssn_changed?) + expect(person).to respond_to(:ssn_was) end - end - describe '#vault_persist_before_save!' do - let(:after_save_dummy_class) do - Class.new(ActiveRecord::Base) do - include Vault::EncryptedModel + context 'with custom attribute types' do + it 'defines an integer attribute' do + Vault::Rails.logical.write("transit/keys/dummy_people_integer_data") + + person = Person.new + person.integer_data = '1' + + expect(person.integer_data).to eq 1 + + person.save + person.reload + + expect(person.integer_data).to eq 1 + end + + it 'defines a float attribute' do + Vault::Rails.logical.write("transit/keys/dummy_people_float_data") + + person = Person.new + person.float_data = '1' + + expect(person.float_data).to eq 1.0 + + person.save + person.reload + + expect(person.float_data).to eq 1.0 + end + + it 'defines a time attribute' do + Vault::Rails.logical.write("transit/keys/dummy_people_time_data") + + time = '2018-10-16 05:00:00 +00:00'.to_time + + person = Person.new + person.time_data = time + + expect(person.time_data).to eq time + + person.save + person.reload + + expect(person.time_data).to eq time end - end - let(:before_save_dummy_class) do - Class.new(ActiveRecord::Base) do - include Vault::EncryptedModel - vault_persist_before_save! + it 'raises an error with unknown attribute type' do + expect do + Person.vault_attribute :unrecognized_attr, type: :unrecognized + end.to raise_error RuntimeError, /Unrecognized/ end end + end + describe '#vault_persist_before_save!' do context "when not used" do it "the model has an after_save callback" do - save_callbacks = after_save_dummy_class._save_callbacks.select do |cb| + save_callbacks = Person._save_callbacks.select do |cb| cb.filter == :__vault_persist_attributes! end @@ -70,11 +101,18 @@ expect(persist_callback.kind).to eq :after end + + it 'calls the correnct callback' do + eager_person = Person.new(ssn: '123-45-6789') + expect(eager_person).to receive(:__vault_persist_attributes!) + + eager_person.save + end end context "when used" do - it "the model does not have a after_save callback" do - save_callbacks = before_save_dummy_class._save_callbacks.select do |cb| + it "the model does not have an after_save callback" do + save_callbacks = EagerPerson._save_callbacks.select do |cb| cb.filter == :__vault_persist_attributes! end @@ -82,7 +120,7 @@ end it "the model has a before_save callback" do - save_callbacks = before_save_dummy_class._save_callbacks.select do |cb| + save_callbacks = EagerPerson._save_callbacks.select do |cb| cb.filter == :__vault_encrypt_attributes! end @@ -93,6 +131,14 @@ expect(persist_callback.kind).to eq :before end + + it 'calls the correct callback' do + eager_person = EagerPerson.new(ssn: '123-45-6789') + expect(eager_person).not_to receive(:__vault_persist_attributes!) + expect(eager_person).to receive(:__vault_encrypt_attributes!) + + eager_person.save + end end end end From dcf0725833fcb5e0521d9d9b5c86339e1a19da7e Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 17 Oct 2018 10:28:48 +0300 Subject: [PATCH 026/143] Increase highest supported version of ActiveRecord to 5.2+ and < 6.0 --- fc-vault-rails.gemspec | 4 ++-- gemfiles/rails_4.2.gemfile.lock | 2 +- lib/vault/encrypted_model.rb | 11 ++++++++++- spec/integration/rails_spec.rb | 2 +- spec/unit/encrypted_model_spec.rb | 10 ++++++---- 5 files changed, 20 insertions(+), 9 deletions(-) diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 12b59e9d..7b215d01 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", [">= 4.2.8", "< 5.1"] + s.add_dependency "activerecord", [">= 4.2.8", "< 6.0"] s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", [">= 4.2.8", "< 5.1"] + s.add_development_dependency "rails", [">= 4.2.8", "< 6.0"] s.add_development_dependency "byebug" s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index d2a14c82..ebfc6796 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (0.6.1) - activerecord (>= 4.2.8, < 5.1) + activerecord (>= 4.2.8, < 6.0) vault (~> 0.7) GEM diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index cdbbd16b..f12136db 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -221,7 +221,16 @@ def __vault_encrypt_attributes! def __vault_encrypt_attribute!(attribute, options) # Only persist changed attributes to minimize requests - this helps # minimize the number of requests to Vault. - return unless changed.include?("#{attribute}") + + if ActiveRecord.version >= Gem::Version.new('5.2.0') + # ActiveRecord 5.2 changes the behaviour of `changed` in `after_*` callbacks + # https://www.ombulabs.com/blog/rails/upgrades/active-record-5-1-api-changes.html + # https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-saved_change_to_attribute + return unless saved_change_to_attribute?(attribute) + else + # Rails >= 4.2.8 and <= 5.1 + return unless changed.include?("#{attribute}") + end key = options[:key] path = options[:path] diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 69ab59d5..d2f57b6b 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -201,7 +201,7 @@ person.ssn = "111-11-1111" - expect(person).to receive(:__vault_load_attributes!).once + expect(person).to receive(:__vault_load_attributes!).once.and_call_original person.reload diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 2ae2465e..86412957 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -66,17 +66,19 @@ it 'defines a time attribute' do Vault::Rails.logical.write("transit/keys/dummy_people_time_data") - time = '2018-10-16 05:00:00 +00:00'.to_time + time = Time.parse('05:10:15 UTC') person = Person.new person.time_data = time - expect(person.time_data).to eq time - person.save person.reload - expect(person.time_data).to eq time + person_time = person.time_data.utc + + expect(person_time.hour).to eq time.hour + expect(person_time.min).to eq time.min + expect(person_time.sec).to eq time.sec end it 'raises an error with unknown attribute type' do From b935c98eeda80971a08b32f417d72e63b9e7536f Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 17 Oct 2018 15:49:15 +0300 Subject: [PATCH 027/143] Attribute Proxy --- lib/vault/encrypted_model.rb | 3 ++ spec/dummy/app/models/person.rb | 6 +++ ...17154000_add_county_and_state_to_people.rb | 10 ++++ spec/dummy/db/schema.rb | 40 +++++++++------- spec/unit/attribute_proxy_spec.rb | 48 +++++++++++++++++++ 5 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 spec/dummy/db/migrate/20181017154000_add_county_and_state_to_people.rb create mode 100644 spec/unit/attribute_proxy_spec.rb diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index f12136db..ac26d7c2 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -107,6 +107,9 @@ def vault_persist_before_save! before_save :__vault_encrypt_attributes! end + def vault_attribute_proxy(m1, m2, oprions={}) + end + # The list of Vault attributes. # # @return [Hash] diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index d53e9a15..7ce946c8 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -3,6 +3,12 @@ class Person < ActiveRecord::Base include Vault::EncryptedModel + vault_attribute :county_encrypted + vault_attribute_proxy :county, :county_encrypted + + vault_attribute :state_encrypted + vault_attribute_proxy :state, :state_encrypted, encrypted_attribute_only: true + vault_attribute :ssn vault_attribute :credit_card, diff --git a/spec/dummy/db/migrate/20181017154000_add_county_and_state_to_people.rb b/spec/dummy/db/migrate/20181017154000_add_county_and_state_to_people.rb new file mode 100644 index 00000000..0eb83ce1 --- /dev/null +++ b/spec/dummy/db/migrate/20181017154000_add_county_and_state_to_people.rb @@ -0,0 +1,10 @@ +class AddCountyAndStateToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :county, :string + add_column :people, :county_encrypted, :string + + add_column :people, :state, :string + add_column :people, :state_encrypted, :string + end +end + diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 54e034cb..3b6e3c89 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,26 +10,30 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181016143900) do +ActiveRecord::Schema.define(version: 2018_10_17_154000) do create_table "people", force: :cascade do |t| - t.string "name" - t.string "ssn_encrypted" - t.string "cc_encrypted" - t.string "details_encrypted" - t.string "business_card_encrypted" - t.string "favorite_color_encrypted" - t.string "non_ascii_encrypted" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "email_encrypted" - t.string "address" - t.string "address_encrypted" - t.date "date_of_birth" - t.string "date_of_birth_encrypted" - t.string "integer_data_encrypted" - t.string "float_data_encrypted" - t.string "time_data_encrypted" + t.string "name" + t.string "ssn_encrypted" + t.string "cc_encrypted" + t.string "details_encrypted" + t.string "business_card_encrypted" + t.string "favorite_color_encrypted" + t.string "non_ascii_encrypted" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "email_encrypted" + t.string "address" + t.string "address_encrypted" + t.date "date_of_birth" + t.string "date_of_birth_encrypted" + t.string "integer_data_encrypted" + t.string "float_data_encrypted" + t.string "time_data_encrypted" + t.string "county" + t.string "county_encrypted" + t.string "state" + t.string "state_encrypted" end end diff --git a/spec/unit/attribute_proxy_spec.rb b/spec/unit/attribute_proxy_spec.rb new file mode 100644 index 00000000..7b3fb2bc --- /dev/null +++ b/spec/unit/attribute_proxy_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +RSpec.describe 'vault_attribute_proxy' do + let(:person) { Person.new } + + context 'with encrypted_attribute_only false' do + it 'fills both attributes' do + county = 'Orange' + person.county = county + + expect(person.county).to eq county + expect(person.county_encrypted).to eq county + end + + it 'reads first from the encrypted attribute' do + person.county_encrypted = 'Yellow' + expect(person.county).to eq('Yellow') + end + + it 'reads from the plain text attribute when encrypted attribute is not available' do + person.county = 'Blue' + person.county_encrypted = nil + + expect(person.county).to eq('Blue') + end + end + + context 'with encrypted_attribute_only true' do + it 'fills only the encrypted attribute' do + person.state = 'California' + + expect(person.state_encrypted).to eq 'California' + expect(person.read_attribute(:state)).to be_nil + end + + it 'reads first from the encrypted attribute' do + person.state_encrypted = 'New York' + expect(person.state).to eq 'New York' + end + + it 'returns nil when encrypted attribute is not available' do + person.state = 'Florida' + person.state_encrypted = nil + + expect(person.state).to be_nil + end + end +end From 9cf34adf6f46e49af4893d2586a91bca78382c2b Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Thu, 18 Oct 2018 16:03:58 +0300 Subject: [PATCH 028/143] Implement #vault_attribute_proxy --- lib/vault/encrypted_model.rb | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index ac26d7c2..aea98b9f 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -107,7 +107,38 @@ def vault_persist_before_save! before_save :__vault_encrypt_attributes! end - def vault_attribute_proxy(m1, m2, oprions={}) + # Define proxy getter and setter methods + # + # Override the getter and setter for a particular non-encrypted attribute + # so that they also call the getter/setter of the encrypted one. + # This ensures that all the code that uses the attribute in question + # also updates/retrieves the encrypted value whenever it is available. + # + # This method is useful if you have a plaintext attribute that you want to replace with a vault attribute. + # During a transition period both attributes can be seamlessly read/changed at the same time. + # + # @param [String | Symbol] non_encrypted_attribute + # The name of original attribute (non-encrypted). + # @param [String | Symbol] encrypted_attribute + # The name of the encrypted attribute. + # This makes sure that the encrypted attribute behaves like a real AR attribute. + # @param [Boolean] (false) encrypted_attribute_only + # Whether to read and write to both encrypted and non-encrypted attributes. + # Useful for when we stop using the non-encrypted one. + def vault_attribute_proxy(non_encrypted_attribute, encrypted_attribute, options={}) + # Only return the encrypted attribute if it's available and encrypted_attribute_only is true. + define_method(non_encrypted_attribute) do + return send(encrypted_attribute) if options[:encrypted_attribute_only] + + send(encrypted_attribute) || super() + end + + # Update only the encrypted attribute if encrypted_attribute_only is true and both attributes otherwise. + define_method("#{non_encrypted_attribute}=") do |value| + super(value) unless options[:encrypted_attribute_only] + + send("#{encrypted_attribute}=", value) + end end # The list of Vault attributes. From 42aab74added22f8cab0fd1882578625b3405d47 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Thu, 18 Oct 2018 16:25:12 +0300 Subject: [PATCH 029/143] Include attribute proxy in README --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 83e7b728..0b048d1c 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,15 @@ Person.where(ssn: "123-45-6789") This is because the database is unaware of the plain-text data (which is part of the security model). +### Vault Attribute Proxy +This method is useful if you have a plaintext attribute that you want to replace with a vault attribute. +During a transition period both attributes can be seamlessly read/changed at the same time. +Then by using the boolean option `encrypted_attribute_only`, you will be able to test if the ciphertext field works as expected before getting rid of the plaintext attribute. +In order to use this method for an attribute you need to add the following row in your model for given plaintext and ciphertext attributes: +```ruby +vault_attribute_proxy :attribute, :attribute_ciphertext, encrypted_attribute_only: true +``` + Development ----------- From aab627df0b72275ca16e28e5abc3032e5146478d Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 24 Oct 2018 13:07:12 +0300 Subject: [PATCH 030/143] Add different names for vault attribute and its column --- spec/dummy/app/models/person.rb | 8 ++++---- spec/unit/attribute_proxy_spec.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index 7ce946c8..1324df9b 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -3,11 +3,11 @@ class Person < ActiveRecord::Base include Vault::EncryptedModel - vault_attribute :county_encrypted - vault_attribute_proxy :county, :county_encrypted + vault_attribute :county_plaintext, encrypted_column: :county_encrypted + vault_attribute_proxy :county, :county_plaintext - vault_attribute :state_encrypted - vault_attribute_proxy :state, :state_encrypted, encrypted_attribute_only: true + vault_attribute :state_plaintext, encrypted_column: :state_encrypted + vault_attribute_proxy :state, :state_plaintext, encrypted_attribute_only: true vault_attribute :ssn diff --git a/spec/unit/attribute_proxy_spec.rb b/spec/unit/attribute_proxy_spec.rb index 7b3fb2bc..c3cd11d4 100644 --- a/spec/unit/attribute_proxy_spec.rb +++ b/spec/unit/attribute_proxy_spec.rb @@ -9,17 +9,17 @@ person.county = county expect(person.county).to eq county - expect(person.county_encrypted).to eq county + expect(person.county_plaintext).to eq county end it 'reads first from the encrypted attribute' do - person.county_encrypted = 'Yellow' + person.county_plaintext = 'Yellow' expect(person.county).to eq('Yellow') end it 'reads from the plain text attribute when encrypted attribute is not available' do person.county = 'Blue' - person.county_encrypted = nil + person.county_plaintext = nil expect(person.county).to eq('Blue') end @@ -29,18 +29,18 @@ it 'fills only the encrypted attribute' do person.state = 'California' - expect(person.state_encrypted).to eq 'California' + expect(person.state_plaintext).to eq 'California' expect(person.read_attribute(:state)).to be_nil end it 'reads first from the encrypted attribute' do - person.state_encrypted = 'New York' + person.state_plaintext = 'New York' expect(person.state).to eq 'New York' end it 'returns nil when encrypted attribute is not available' do person.state = 'Florida' - person.state_encrypted = nil + person.state_plaintext = nil expect(person.state).to be_nil end From 9d0f01cb5a4bdfa2860b17fbd7c88f7f2bfe0e29 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 24 Oct 2018 14:39:57 +0300 Subject: [PATCH 031/143] Bump to v0.7.0 --- CHANGELOG.md | 9 +++++++++ gemfiles/rails_4.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 484f2c68..d11b021a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Vault Rails Changelog +## v0.7.0 (October 24, 2018) + +NOTABLE CHANGES + - Use ActiveRecord Attribute API to implement encrypted attributes + - Add support for ActiveRecord >= 5.2 and ActiveRecord < 6.0 + + NEW FEATURES + - AttributeProxy for easy transition between plaintext and ciphertext attributes + ## v0.6.1 (October 16, 2018) NEW FEATURES diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index ebfc6796..d8c9cec7 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.6.1) + fc-vault-rails (0.7.0) activerecord (>= 4.2.8, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index c485dd3f..b6a47333 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.6.1" + VERSION = "0.7.0" end end From a56769c28fdbeab3e415eed0d5fcf4a46e675c47 Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Wed, 24 Oct 2018 15:09:00 +0300 Subject: [PATCH 032/143] Fix indentation in CircleCI config --- .circleci/config.yml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index e07404ca..51f39e38 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -66,8 +66,7 @@ jobs: - run: shell: /bin/bash -l - command: | - rvm use --default 2.5.1 + command: rvm use --default 2.5.1 - run: name: Install Gem Versioner @@ -91,7 +90,6 @@ jobs: <<: *defaults steps: - - checkout - run: yum -y install curl - run: yum clean all @@ -104,9 +102,8 @@ jobs: chmod 600 ~/.gem/credentials - run: - shell: /bin/bash -l - command: | - rvm use --default 2.5.1 + shell: /bin/bash -l + command: rvm use --default 2.5.1 - run: name: Install Gem Versioner From 3df72c19707b0af828f4d4bde219114a74d9744c Mon Sep 17 00:00:00 2001 From: Elena Tanasoiu Date: Mon, 29 Oct 2018 17:09:54 +0000 Subject: [PATCH 033/143] Mention how to setup the test db --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 0b048d1c..6edbff9b 100644 --- a/README.md +++ b/README.md @@ -296,3 +296,10 @@ Important Notes: - **All new features must include test coverage.** At a bare minimum, Unit tests are required. It is preferred if you include acceptance tests as well. - **The tests must be be idempotent.** The HTTP calls made during a test should be able to be run over and over. - **Tests are order independent.** The default RSpec configuration randomizes the test order, so this should not be a problem. + + +Getting tests to run +-------------------- +``` +$ bundle exec db:schema:load +``` \ No newline at end of file From 7cf0581cb04c2027d5240e1ae9cd841aef77d1e8 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 12 Nov 2018 14:24:42 +0000 Subject: [PATCH 034/143] Update changelog for 0.7.x We include the 0.6.2 and 0.6.3 changes from the 0.6.x branch and update the 0.7.x description to mention the breaking changes from 0.6 to 0.7. --- CHANGELOG.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d11b021a..31ebd26e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,28 @@ NOTABLE CHANGES - Use ActiveRecord Attribute API to implement encrypted attributes - Add support for ActiveRecord >= 5.2 and ActiveRecord < 6.0 - NEW FEATURES - - AttributeProxy for easy transition between plaintext and ciphertext attributes +BREAKING CHANGES + - `vault_attribute_proxy` is included with `Vault::EncryptedModel` by + default, there is no `Valut::AttributeProxy` module any more. + - type information is now specified on `vault_attribute` definitions + instead of the `vault_attribute_proxy` definitions. + +## v0.6.3 (October 31, 2018) + +NEW FEATURES +- Allow specifying type information on `vault_attribute_proxy` definitions. + This allows the proxied attribute to convert between strings (what all + values ultimately are when send to vault for encryption) and the typed + representation that we'd otherwise get from a traditional activerecord + database-backed attribute. + +## v0.6.2 (October 30, 2018) + +NEW FEATURES +- Introduce `vault_attribute_proxy` via including Vault::AttributeProxy. + This acts to unify an existing plaintext column with a new encryped + column defined as a `vault_attribute`. Allowing a staged transition to + a fully encrypted attribute at a later date. ## v0.6.1 (October 16, 2018) From 98a6a95253a9ed82d4b15ab6319dcf3a7283d840 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 12 Nov 2018 12:46:10 +0000 Subject: [PATCH 035/143] Test against rails 5, 5.1, and 5.2 not just 4.2 We add the new rails gemfiles using the `appraisals` gem. This means that we can run `bundle exec appraisal rake` locally to run the specs against rails 4.2, 5, 5.1, and 5.2. We've also added these gemfiles to the `.travis.yml` so these rails versions are added to the ruby version test matrix that we run on CI. Note that although we use CircleCI to run our CI tests, we use the `wwtd` gem to read the `.travis.yml` config and do what it would do. This is confusing, but it works. --- .travis.yml | 3 + Appraisals | 9 ++ gemfiles/rails_5.0.gemfile | 7 ++ gemfiles/rails_5.0.gemfile.lock | 155 ++++++++++++++++++++++++++++++ gemfiles/rails_5.1.gemfile | 7 ++ gemfiles/rails_5.1.gemfile.lock | 155 ++++++++++++++++++++++++++++++ gemfiles/rails_5.2.gemfile | 7 ++ gemfiles/rails_5.2.gemfile.lock | 163 ++++++++++++++++++++++++++++++++ 8 files changed, 506 insertions(+) create mode 100644 gemfiles/rails_5.0.gemfile create mode 100644 gemfiles/rails_5.0.gemfile.lock create mode 100644 gemfiles/rails_5.1.gemfile create mode 100644 gemfiles/rails_5.1.gemfile.lock create mode 100644 gemfiles/rails_5.2.gemfile create mode 100644 gemfiles/rails_5.2.gemfile.lock diff --git a/.travis.yml b/.travis.yml index bbc447ba..29f27020 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,9 @@ sudo: false gemfile: - Gemfile - gemfiles/rails_4.2.gemfile + - gemfiles/rails_5.0.gemfile + - gemfiles/rails_5.1.gemfile + - gemfiles/rails_5.2.gemfile before_install: - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip diff --git a/Appraisals b/Appraisals index 72063733..e91a0573 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,12 @@ appraise "rails-4.2" do gem "rails", "~> 4.2.8" end +appraise "rails-5.0" do + gem "rails", "~> 5.0.0" +end +appraise "rails-5.1" do + gem "rails", "~> 5.1.0" +end +appraise "rails-5.2" do + gem "rails", "~> 5.2.0" +end diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile new file mode 100644 index 00000000..10f52e7a --- /dev/null +++ b/gemfiles/rails_5.0.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 5.0.0" + +gemspec path: "../" diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock new file mode 100644 index 00000000..86b5c9b1 --- /dev/null +++ b/gemfiles/rails_5.0.gemfile.lock @@ -0,0 +1,155 @@ +PATH + remote: .. + specs: + fc-vault-rails (0.7.0) + activerecord (>= 4.2.8, < 6.0) + vault (~> 0.7) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.0.7) + actionpack (= 5.0.7) + nio4r (>= 1.2, < 3.0) + websocket-driver (~> 0.6.1) + actionmailer (5.0.7) + actionpack (= 5.0.7) + actionview (= 5.0.7) + activejob (= 5.0.7) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.0.7) + actionview (= 5.0.7) + activesupport (= 5.0.7) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.7) + activesupport (= 5.0.7) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.0.7) + activesupport (= 5.0.7) + globalid (>= 0.3.6) + activemodel (5.0.7) + activesupport (= 5.0.7) + activerecord (5.0.7) + activemodel (= 5.0.7) + activesupport (= 5.0.7) + arel (~> 7.0) + activesupport (5.0.7) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (7.1.4) + aws-sigv4 (1.0.3) + builder (3.2.3) + byebug (10.0.2) + coderay (1.1.2) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubis (2.7.0) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + method_source (0.9.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + pry (0.12.1) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rack (2.0.6) + rack-test (0.6.3) + rack (>= 1.0) + rails (5.0.7) + actioncable (= 5.0.7) + actionmailer (= 5.0.7) + actionpack (= 5.0.7) + actionview (= 5.0.7) + activejob (= 5.0.7) + activemodel (= 5.0.7) + activerecord (= 5.0.7) + activesupport (= 5.0.7) + bundler (>= 1.3.0) + railties (= 5.0.7) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.0.7) + actionpack (= 5.0.7) + activesupport (= 5.0.7) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + vault (0.12.0) + aws-sigv4 + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + wwtd (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.1) + bundler + byebug + fc-vault-rails! + pry + rails (~> 5.0.0) + rake (~> 10.0) + rspec (~> 3.2) + sqlite3 + wwtd + +BUNDLED WITH + 1.16.6 diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile new file mode 100644 index 00000000..6100e830 --- /dev/null +++ b/gemfiles/rails_5.1.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 5.1.0" + +gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock new file mode 100644 index 00000000..b1e8c9af --- /dev/null +++ b/gemfiles/rails_5.1.gemfile.lock @@ -0,0 +1,155 @@ +PATH + remote: .. + specs: + fc-vault-rails (0.7.0) + activerecord (>= 4.2.8, < 6.0) + vault (~> 0.7) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.1.6) + actionpack (= 5.1.6) + nio4r (~> 2.0) + websocket-driver (~> 0.6.1) + actionmailer (5.1.6) + actionpack (= 5.1.6) + actionview (= 5.1.6) + activejob (= 5.1.6) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.1.6) + actionview (= 5.1.6) + activesupport (= 5.1.6) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.1.6) + activesupport (= 5.1.6) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.1.6) + activesupport (= 5.1.6) + globalid (>= 0.3.6) + activemodel (5.1.6) + activesupport (= 5.1.6) + activerecord (5.1.6) + activemodel (= 5.1.6) + activesupport (= 5.1.6) + arel (~> 8.0) + activesupport (5.1.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (8.0.0) + aws-sigv4 (1.0.3) + builder (3.2.3) + byebug (10.0.2) + coderay (1.1.2) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubi (1.7.1) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + method_source (0.9.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + pry (0.12.1) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rack (2.0.6) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.1.6) + actioncable (= 5.1.6) + actionmailer (= 5.1.6) + actionpack (= 5.1.6) + actionview (= 5.1.6) + activejob (= 5.1.6) + activemodel (= 5.1.6) + activerecord (= 5.1.6) + activesupport (= 5.1.6) + bundler (>= 1.3.0) + railties (= 5.1.6) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.1.6) + actionpack (= 5.1.6) + activesupport (= 5.1.6) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + vault (0.12.0) + aws-sigv4 + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + wwtd (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.1) + bundler + byebug + fc-vault-rails! + pry + rails (~> 5.1.0) + rake (~> 10.0) + rspec (~> 3.2) + sqlite3 + wwtd + +BUNDLED WITH + 1.16.6 diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile new file mode 100644 index 00000000..5a706dcb --- /dev/null +++ b/gemfiles/rails_5.2.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 5.2.0" + +gemspec path: "../" diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock new file mode 100644 index 00000000..86a01d6c --- /dev/null +++ b/gemfiles/rails_5.2.gemfile.lock @@ -0,0 +1,163 @@ +PATH + remote: .. + specs: + fc-vault-rails (0.7.0) + activerecord (>= 4.2.8, < 6.0) + vault (~> 0.7) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.2.1) + actionpack (= 5.2.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.1) + actionview (= 5.2.1) + activesupport (= 5.2.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.2.1) + activesupport (= 5.2.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.2.1) + activesupport (= 5.2.1) + globalid (>= 0.3.6) + activemodel (5.2.1) + activesupport (= 5.2.1) + activerecord (5.2.1) + activemodel (= 5.2.1) + activesupport (= 5.2.1) + arel (>= 9.0) + activestorage (5.2.1) + actionpack (= 5.2.1) + activerecord (= 5.2.1) + marcel (~> 0.3.1) + activesupport (5.2.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (9.0.0) + aws-sigv4 (1.0.3) + builder (3.2.3) + byebug (10.0.2) + coderay (1.1.2) + concurrent-ruby (1.1.3) + crass (1.0.4) + diff-lcs (1.3) + erubi (1.7.1) + globalid (0.4.1) + activesupport (>= 4.2.0) + i18n (1.1.1) + concurrent-ruby (~> 1.0) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.2) + mini_mime (1.0.1) + mini_portile2 (2.3.0) + minitest (5.11.3) + nio4r (2.3.1) + nokogiri (1.8.5) + mini_portile2 (~> 2.3.0) + pry (0.12.1) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rack (2.0.6) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (5.2.1) + actioncable (= 5.2.1) + actionmailer (= 5.2.1) + actionpack (= 5.2.1) + actionview (= 5.2.1) + activejob (= 5.2.1) + activemodel (= 5.2.1) + activerecord (= 5.2.1) + activestorage (= 5.2.1) + activesupport (= 5.2.1) + bundler (>= 1.3.0) + railties (= 5.2.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (5.2.1) + actionpack (= 5.2.1) + activesupport (= 5.2.1) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + vault (0.12.0) + aws-sigv4 + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + wwtd (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.1) + bundler + byebug + fc-vault-rails! + pry + rails (~> 5.2.0) + rake (~> 10.0) + rspec (~> 3.2) + sqlite3 + wwtd + +BUNDLED WITH + 1.16.6 From bdb80c84a7bf0bb2c9101a6dd1c7316dd955666f Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 12 Nov 2018 13:13:28 +0000 Subject: [PATCH 036/143] Fix bug with `vault_persist_before_save!` and rails 5.2 In rails 5.2 the behaviour of dirty tracking methods changed when they are run during callbacks. Methods like `_changed?` and `changes` do the same thing in callbacks run before save, but will behave differently in callbacks run after save. We'd already written `__vault_encrypt_attribute` to understand this and use the `saved_change_to_attribute?` method instead of `_changed?` method for rails 5.2. Unfortunately, this is only the correct approach when we're running `__vault_encrypt_attribute` in an `after_save` callback, which we're not if we have run `vault_persist_before_save!`. This means `__vault_encrypte_attribute` is run in a `before_save` callback where the `saved_change_to_attribute?` methods don't do what we want (because we haven't saved any changes yet). In this case we do want to use the old API that the raisl < 5.2 branch uses. --- CHANGELOG.md | 6 ++++++ lib/vault/encrypted_model.rb | 10 +++++----- spec/unit/encrypted_model_spec.rb | 24 +++++++++++++++++++++--- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ebd26e..5f772833 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Vault Rails Changelog +## Unreleased + +BUG FIXES +- Actually persist encrypted attributes when using + `vault_persist_before_save!` in rails 5.2 + ## v0.7.0 (October 24, 2018) NOTABLE CHANGES diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index aea98b9f..a3743cdb 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -228,7 +228,7 @@ def __vault_load_attribute!(attribute, options) # on this model. # @return [true] def __vault_persist_attributes! - changes = __vault_encrypt_attributes! + changes = __vault_encrypt_attributes!(in_after_save: true) # If there are any changes to the model, update them all at once, # skipping any callbacks and validation. This is okay, because we are @@ -238,11 +238,11 @@ def __vault_persist_attributes! true end - def __vault_encrypt_attributes! + def __vault_encrypt_attributes!(in_after_save: false) changes = {} self.class.__vault_attributes.each do |attribute, options| - if c = self.__vault_encrypt_attribute!(attribute, options) + if c = self.__vault_encrypt_attribute!(attribute, options, in_after_save: in_after_save) changes.merge!(c) end end @@ -252,11 +252,11 @@ def __vault_encrypt_attributes! # Encrypt a single attribute using Vault and persist back onto the # encrypted attribute value. - def __vault_encrypt_attribute!(attribute, options) + def __vault_encrypt_attribute!(attribute, options, in_after_save: false) # Only persist changed attributes to minimize requests - this helps # minimize the number of requests to Vault. - if ActiveRecord.version >= Gem::Version.new('5.2.0') + if in_after_save && ActiveRecord.version >= Gem::Version.new('5.2.0') # ActiveRecord 5.2 changes the behaviour of `changed` in `after_*` callbacks # https://www.ombulabs.com/blog/rails/upgrades/active-record-5-1-api-changes.html # https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-saved_change_to_attribute diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 86412957..f93b0460 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -105,10 +105,19 @@ end it 'calls the correnct callback' do - eager_person = Person.new(ssn: '123-45-6789') - expect(eager_person).to receive(:__vault_persist_attributes!) + person = Person.new(ssn: '123-45-6789') + expect(person).to receive(:__vault_persist_attributes!) - eager_person.save + person.save + end + + it 'encrypts the attribute if it has been saved' do + person = Person.new(ssn: '123-45-6789') + expect(Vault::Rails).to receive(:encrypt).with('transit', 'dummy_people_ssn', anything, anything, anything).and_call_original + + person.save + + expect(person.ssn_encrypted).not_to be_nil end end @@ -141,6 +150,15 @@ eager_person.save end + + it 'encrypts the attribute if it has been saved' do + eager_person = EagerPerson.new(ssn: '123-45-6789') + expect(Vault::Rails).to receive(:encrypt).with('transit', 'dummy_people_ssn',anything,anything,anything).and_call_original + + eager_person.save + + expect(eager_person.ssn_encrypted).not_to be_nil + end end end end From f99aebdb254f103631f6a5fa1f8d7b4138178cc1 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 12 Nov 2018 15:06:10 +0000 Subject: [PATCH 037/143] Clear up Person vs. EagerPerson when testing vault_persist_before_save! We use `Person` in the "when not used" context because that model class has not had `vault_persist_before_save!` called on it. `EagerPerson` has and that's why we use it in the "when used" context. It'd be clearer if we could explicitly call it in the spec, but there's no clean up option to unset it so we need separate models. --- spec/unit/encrypted_model_spec.rb | 40 ++++++++++++++++++------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index f93b0460..78ac780f 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -91,8 +91,11 @@ describe '#vault_persist_before_save!' do context "when not used" do + # Person hasn't had `vault_persist_before_save!` called on it + let(:model_class) { Person } + it "the model has an after_save callback" do - save_callbacks = Person._save_callbacks.select do |cb| + save_callbacks = model_class._save_callbacks.select do |cb| cb.filter == :__vault_persist_attributes! end @@ -104,26 +107,29 @@ expect(persist_callback.kind).to eq :after end - it 'calls the correnct callback' do - person = Person.new(ssn: '123-45-6789') - expect(person).to receive(:__vault_persist_attributes!) + it 'calls the correct callback' do + record = model_class.new(ssn: '123-45-6789') + expect(record).to receive(:__vault_persist_attributes!) - person.save + record.save end it 'encrypts the attribute if it has been saved' do - person = Person.new(ssn: '123-45-6789') + record = model_class.new(ssn: '123-45-6789') expect(Vault::Rails).to receive(:encrypt).with('transit', 'dummy_people_ssn', anything, anything, anything).and_call_original - person.save + record.save - expect(person.ssn_encrypted).not_to be_nil + expect(record.ssn_encrypted).not_to be_nil end end context "when used" do + # EagerPerson has had `vault_persist_before_save!` called on it + let(:model_class) { EagerPerson } + it "the model does not have an after_save callback" do - save_callbacks = EagerPerson._save_callbacks.select do |cb| + save_callbacks = model_class._save_callbacks.select do |cb| cb.filter == :__vault_persist_attributes! end @@ -131,7 +137,7 @@ end it "the model has a before_save callback" do - save_callbacks = EagerPerson._save_callbacks.select do |cb| + save_callbacks = model_class._save_callbacks.select do |cb| cb.filter == :__vault_encrypt_attributes! end @@ -144,20 +150,20 @@ end it 'calls the correct callback' do - eager_person = EagerPerson.new(ssn: '123-45-6789') - expect(eager_person).not_to receive(:__vault_persist_attributes!) - expect(eager_person).to receive(:__vault_encrypt_attributes!) + record = model_class.new(ssn: '123-45-6789') + expect(record).not_to receive(:__vault_persist_attributes!) + expect(record).to receive(:__vault_encrypt_attributes!) - eager_person.save + record.save end it 'encrypts the attribute if it has been saved' do - eager_person = EagerPerson.new(ssn: '123-45-6789') + record = model_class.new(ssn: '123-45-6789') expect(Vault::Rails).to receive(:encrypt).with('transit', 'dummy_people_ssn',anything,anything,anything).and_call_original - eager_person.save + record.save - expect(eager_person.ssn_encrypted).not_to be_nil + expect(record.ssn_encrypted).not_to be_nil end end end From 606dcb39386b3574a614a9ba5843d6295d35ce7b Mon Sep 17 00:00:00 2001 From: Danny Radev Date: Tue, 30 Oct 2018 11:51:09 +0200 Subject: [PATCH 038/143] Improve loading of encrypted attributes. --- .gitignore | 1 + lib/vault/encrypted_model.rb | 40 +++++++++++++++++++++++++++------- spec/integration/rails_spec.rb | 20 +++++++++++++---- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 3dd1753d..f94de97f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ /test/tmp/ /test/version_tmp/ /tmp/ +/.byebug_history ## Specific to RubyMotion: .dat* diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index a3743cdb..0cc25f10 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -79,14 +79,26 @@ def vault_attribute(attribute_name, options = {}) # Getter define_method(attribute_name) do - read_attribute(attribute_name) || __vault_load_attribute!(attribute_name, self.class.__vault_attributes[attribute_name]) + unless __vault_loaded_attributes.include?(attribute_name) + __vault_load_attribute!(attribute_name, self.class.__vault_attributes[attribute_name]) + end + + read_attribute(attribute_name) end # Setter define_method("#{attribute_name}=") do |value| + # Prevent the attribute from loading when a value is provided before + # the attribute is loaded from Vault but only if the model is initialized + __vault_loaded_attributes << attribute_name + # Force the update of the attribute, to be consistent with old behaviour + cast_value = write_attribute(attribute_name, value) + + # Rails 4.2 resets the dirty state if write_attribute is called with the same value after attribute_will_change attribute_will_change!(attribute_name) - write_attribute(attribute_name, value) + + cast_value end # Make a note of this attribute so we can use it in the future (maybe). @@ -181,7 +193,7 @@ def vault_lazy_decrypt! included do # After a resource has been initialized, immediately communicate with # Vault and decrypt any attributes unless vault_lazy_decrypt is set. - after_initialize :__vault_load_attributes! + after_initialize :__vault_initialize_attributes! # After we save the record, persist all the values to Vault and reload # them attributes from Vault to ensure we have the proper attributes set. @@ -191,10 +203,18 @@ def vault_lazy_decrypt! # before theirs, resulting in attributes that are not persisted. after_save :__vault_persist_attributes! - # Decrypt all the attributes from Vault. - def __vault_load_attributes! + def __vault_loaded_attributes + @__vault_loaded_attributes ||= Set.new + end + + def __vault_initialize_attributes! return if self.class.vault_lazy_decrypt? + __vault_load_attributes! + end + + # Decrypt all the attributes from Vault. + def __vault_load_attributes! self.class.__vault_attributes.each do |attribute, options| self.__vault_load_attribute!(attribute, options) end @@ -203,7 +223,7 @@ def __vault_load_attributes! # Decrypt and load a single attribute from Vault. def __vault_load_attribute!(attribute, options) # If the user provided a value for the attribute, do not try to load it from Vault - return unless read_attribute(attribute).nil? + return if __vault_loaded_attributes.include?(attribute) key = options[:key] path = options[:path] @@ -220,6 +240,8 @@ def __vault_load_attribute!(attribute, options) # Deserialize the plaintext value, if a serializer exists plaintext = serializer.decode(plaintext) if serializer + __vault_loaded_attributes << attribute + # Write the virtual attribute with the plaintext value write_attribute(attribute, plaintext) end @@ -233,7 +255,7 @@ def __vault_persist_attributes! # If there are any changes to the model, update them all at once, # skipping any callbacks and validation. This is okay, because we are # already in a transaction due to the callback. - self.update_columns(changes) if !changes.empty? + self.update_columns(changes) unless changes.empty? true end @@ -301,7 +323,9 @@ def reload(*) write_attribute(attribute, nil) end - self.__vault_load_attributes! + __vault_loaded_attributes.clear + + __vault_initialize_attributes! clear_changes_information end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index d2f57b6b..d3dfcdd9 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -36,7 +36,7 @@ expect(person.ssn_was).to eq("123-45-6789") end - it "allows attributes to be unset" do + it "allows attributes to be updated with nil values" do person = Person.create!(ssn: "123-45-6789") person.update_attributes!(ssn: nil) person.reload @@ -44,6 +44,12 @@ expect(person.ssn).to be(nil) end + it "allows attributes to be unset" do + person = Person.create!(ssn: "123-45-6789") + person.ssn = nil + expect(person.ssn).to be(nil) + end + it "allows saving without validations" do person = Person.new(ssn: "123-456-7890") person.save(validate: false) @@ -143,8 +149,7 @@ person = LazyPerson.create!(ssn: "123-45-6789", non_ascii: 'some text') expect(person.ssn).not_to be_nil - expect(person).to receive(:__vault_load_attributes!).once - expect(person).not_to receive(:__vault_load_attribute!) + expect(person).not_to receive(:__vault_load_attributes!) person.reload expect(person.read_attribute(:ssn)).to be nil @@ -187,6 +192,12 @@ expect(person.ssn).to be(nil) end + it "allows attributes to be unset" do + person = LazyPerson.create!(ssn: "123-45-6789") + person.ssn = nil + expect(person.ssn).to be(nil) + end + it "allows attributes to be blank" do person = LazyPerson.create!(ssn: "123-45-6789") person.update_attributes!(ssn: "") @@ -201,7 +212,8 @@ person.ssn = "111-11-1111" - expect(person).to receive(:__vault_load_attributes!).once.and_call_original + expect(person).to receive(:__vault_initialize_attributes!).once.and_call_original + expect(person).to receive(:__vault_load_attribute!).once.with(:ssn, any_args).and_call_original person.reload From 87de1dfda36de043c9709e834e20b142ed4c161b Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 17 Oct 2018 14:34:17 +0300 Subject: [PATCH 039/143] Add methods for batch decryption and encryption --- lib/vault/rails.rb | 96 +++++++++++++++++++++++++++++++-- spec/unit/rails_spec.rb | 114 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 202 insertions(+), 8 deletions(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index d20fa2d3..300c5314 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -78,8 +78,8 @@ def respond_to_missing?(m, include_private = false) def encrypt(path, key, plaintext, client = self.client, convergent = false) return plaintext if plaintext.blank? - path = path.to_s if !path.is_a?(String) - key = key.to_s if !key.is_a?(String) + path = path.to_s + key = key.to_s with_retries do if self.enabled? @@ -92,6 +92,24 @@ def encrypt(path, key, plaintext, client = self.client, convergent = false) end end + # works only with convergent encryption + def batch_encrypt(path, key, plaintexts, client = self.client) + return [] if plaintexts.empty? + + path = path.to_s + key = key.to_s + + with_retries do + results = if self.enabled? + self.vault_batch_encrypt(path, key, plaintexts, client) + else + self.memory_batch_encrypt(path, key, plaintexts, client) + end + + results.map { |result| self.force_encoding(result) } + end + end + # Decrypt the given ciphertext data using the provided mount and key. # # @param [String] path @@ -110,8 +128,8 @@ def decrypt(path, key, ciphertext, client = self.client, convergent = false) return ciphertext end - path = path.to_s if !path.is_a?(String) - key = key.to_s if !key.is_a?(String) + path = path.to_s + key = key.to_s with_retries do if self.enabled? @@ -124,6 +142,24 @@ def decrypt(path, key, ciphertext, client = self.client, convergent = false) end end + # works only with convergent encryption + def batch_decrypt(path, key, ciphertexts, client = self.client) + return [] if ciphertexts.empty? + + path = path.to_s + key = key.to_s + + with_retries do + results = if self.enabled? + self.vault_batch_decrypt(path, key, ciphertexts, client) + else + self.memory_batch_decrypt(path, key, ciphertexts, client) + end + + results.map { |result| self.force_encoding(result) } + end + end + # Get the serializer that corresponds to the given key. If the key does not # correspond to a known serializer, an exception will be raised. # @@ -162,6 +198,11 @@ def memory_encrypt(path, key, plaintext, _client, convergent) Base64.strict_encode64(iv + cipher.update(plaintext) + cipher.final) end + # Perform in-memory encryption. This is useful for testing and development. + def memory_batch_encrypt(path, key, plaintexts, _client) + plaintexts.map { |plaintext| memory_encrypt(path, key, ciphertext, _client, true) } + end + # Perform in-memory decryption. This is useful for testing and development. def memory_decrypt(path, key, ciphertext, _client, convergent) log_warning(DEV_WARNING) if self.in_memory_warnings_enabled? @@ -180,6 +221,11 @@ def memory_decrypt(path, key, ciphertext, _client, convergent) cipher.update(ciphertext) + cipher.final end + def memory_batch_decrypt(path, key, ciphertexts, _client) + ciphertexts.map { |ciphertext| memory_decrypt(path, key, ciphertext, _client, true) } + end + + # Perform encryption using Vault. This will raise exceptions if Vault is # unavailable. def vault_encrypt(path, key, plaintext, client, convergent) @@ -202,6 +248,29 @@ def vault_encrypt(path, key, plaintext, client, convergent) secret.data[:ciphertext] end + def vault_batch_encrypt(path, key, plaintexts, client) + return [] if plaintexts.empty? + + route = File.join(path, 'encrypt', key) + + options = { + convergent_encryption: true, + derived: true + } + + batch_input = plaintexts.map do |plaintext| + { + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + plaintext: Base64.strict_encode64(plaintext) + } + end + + options.merge!(batch_input: batch_input) + + secret = client.logical.write(route, options) + secret.data[:batch_results].map { |result| result[:ciphertext] } + end + # Perform decryption using Vault. This will raise exceptions if Vault is # unavailable. def vault_decrypt(path, key, ciphertext, client, convergent) @@ -221,6 +290,25 @@ def vault_decrypt(path, key, ciphertext, client, convergent) Base64.strict_decode64(secret.data[:plaintext]) end + def vault_batch_decrypt(path, key, ciphertexts, client) + return [] if ciphertexts.empty? + + route = File.join(path, 'decrypt', key) + + + batch_input = ciphertexts.map do |ciphertext| + { + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + ciphertext: ciphertext + } + end + + options = { batch_input: batch_input } + + secret = client.logical.write(route, options) + secret.data[:batch_results].map { |result| Base64.strict_decode64(result[:plaintext]) } + end + # The symmetric key for the given params. # @return [String] def memory_key_for(path, key) diff --git a/spec/unit/rails_spec.rb b/spec/unit/rails_spec.rb index 9b39f727..bc576315 100644 --- a/spec/unit/rails_spec.rb +++ b/spec/unit/rails_spec.rb @@ -28,7 +28,7 @@ allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16) end - it 'sends the correct paramaters to vault client' do + it 'sends the correct parameters to vault client' do expected_route = 'path/encrypt/key' expected_options = { plaintext: Base64.strict_encode64('plaintext'), @@ -50,7 +50,7 @@ allow(Vault::Rails).to receive(:enabled?).and_return(true) end - it 'sends the correct paramaters to vault client' do + it 'sends the correct parameters to vault client' do expected_route = 'path/encrypt/key' expected_options = { plaintext: Base64.strict_encode64('plaintext') } @@ -70,7 +70,7 @@ allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16) end - it 'sends the correct paramaters to vault client' do + it 'sends the correct parameters to vault client' do expected_route = 'path/decrypt/key' expected_options = { ciphertext: 'ciphertext', @@ -90,7 +90,7 @@ allow(Vault::Rails).to receive(:enabled?).and_return(true) end - it 'sends the correct paramaters to vault client' do + it 'sends the correct parameters to vault client' do expected_route = 'path/decrypt/key' expected_options = { ciphertext: 'ciphertext' } @@ -102,4 +102,110 @@ end end end + + describe '.batch_encrypt' do + before do + allow(Vault::Rails).to receive(:enabled?).and_return(true) + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16) + end + + it 'sends the correct parameters to vault client' do + expected_route = 'path/encrypt/key' + expected_options = { + batch_input: [ + { + plaintext: Base64.strict_encode64('plaintext1'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + plaintext: Base64.strict_encode64('plaintext2'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + convergent_encryption: true, + derived: true + } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.batch_encrypt('path', 'key', ['plaintext1', 'plaintext2'], Vault::Rails.client) + end + + it 'parses the response from vault client correctly' do + expected_route = 'path/encrypt/key' + expected_options = { + batch_input: [ + { + plaintext: Base64.strict_encode64('plaintext1'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + plaintext: Base64.strict_encode64('plaintext2'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + convergent_encryption: true, + derived: true + } + + allow(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(instance_double('Vault::Secret', data: {:batch_results=>[{:ciphertext=>'ciphertext1'}, {:ciphertext=>'ciphertext2'}]})) + + expect(Vault::Rails.batch_encrypt('path', 'key', ['plaintext1', 'plaintext2'], Vault::Rails.client)).to eq(%w(ciphertext1 ciphertext2)) + end + end + + describe '.batch_decrypt' do + before do + allow(Vault::Rails).to receive(:enabled?).and_return(true) + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16) + end + + it 'sends the correct parameters to vault client' do + expected_route = 'path/decrypt/key' + expected_options = { + batch_input: [ + { + ciphertext: 'ciphertext1', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + ciphertext: 'ciphertext2', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.batch_decrypt('path', 'key', ['ciphertext1', 'ciphertext2'], Vault::Rails.client) + end + + it 'parses the response from vault client correctly' do + expected_route = 'path/decrypt/key' + expected_options = { + batch_input: [ + { + ciphertext: 'ciphertext1', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + ciphertext: 'ciphertext2', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + } + + allow(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(instance_double('Vault::Secret', data: {:batch_results=>[{:plaintext=>'cGxhaW50ZXh0MQ=='}, {:plaintext=>'cGxhaW50ZXh0Mg=='}]})) + + expect(Vault::Rails.batch_decrypt('path', 'key', ['ciphertext1', 'ciphertext2'], Vault::Rails.client)).to eq( %w(plaintext1 plaintext2)) # in that order + end + end end From 6bdb766beadec3001d9f6cef690e7d7647e9552c Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Fri, 9 Nov 2018 11:04:33 +0200 Subject: [PATCH 040/143] Add README for batch encryption and decryption --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6edbff9b..1e36aac2 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,15 @@ vault_attribute :ssn, - **Note** Convergent encryption significantly weakens the security that encryption provides. Use this with caution! +### Batch encryption and decryption +There is an option to encrypt or decrypt multiple strings at once. +All items to be encrypted/decrypted should use the same path, key and client. + +``` ruby +Vault::Rails.batch_decrypt(path, key, , client) +Vault::Rails.batch_encrypt(path, key, , client) +``` + ### Searching Encrypted Attributes Because each column is uniquely encrypted, it is not possible to search for a @@ -302,4 +311,4 @@ Getting tests to run -------------------- ``` $ bundle exec db:schema:load -``` \ No newline at end of file +``` From 30092d1e73deede5a86168a690fb008d21ea8c2e Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Wed, 21 Nov 2018 10:46:15 +0000 Subject: [PATCH 041/143] Introduce deprecation warnings for 0.6->0.7 differences This means giving people a warning that `type` is meaningless on `vault_attribute_proxy` now and they should move their type information to the `vault_attribute` definition. This also means reintroducing the `Vault::AttributeProxy` module and having it do nothing but emit a deprecation warning telling you to stop including it. --- CHANGELOG.md | 5 +++++ lib/vault/attribute_proxy.rb | 12 ++++++++++++ lib/vault/encrypted_model.rb | 3 +++ lib/vault/rails.rb | 1 + 4 files changed, 21 insertions(+) create mode 100644 lib/vault/attribute_proxy.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f772833..876532a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +NEW FEATURES +- Introduce deprecation warnings for the breaking changes between 0.6 and + 0.7. This includes adding back `Vault::AttributeProxy` as an empty + module that generates a deprecation warning. + BUG FIXES - Actually persist encrypted attributes when using `vault_persist_before_save!` in rails 5.2 diff --git a/lib/vault/attribute_proxy.rb b/lib/vault/attribute_proxy.rb new file mode 100644 index 00000000..db5e3594 --- /dev/null +++ b/lib/vault/attribute_proxy.rb @@ -0,0 +1,12 @@ +require "active_support/concern" +require "active_support/deprecation" + +module Vault + module AttributeProxy + extend ActiveSupport::Concern + + included do + ActiveSupport::Deprecation.warn('Vault::AttributeProxy is no longer required, `vault_attribute_proxy` comes via `Vault::EncryptedModel` so you can remove `include Vault::AttributeProxy` from your model.') + end + end +end diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 0cc25f10..35f78033 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -138,6 +138,9 @@ def vault_persist_before_save! # Whether to read and write to both encrypted and non-encrypted attributes. # Useful for when we stop using the non-encrypted one. def vault_attribute_proxy(non_encrypted_attribute, encrypted_attribute, options={}) + if options[:type].present? + ActiveSupport::Deprecation.warn('The `type` option on `vault_attribute_proxy` is now ignored. To specify type information you should move the `type` option onto the `vault_attribute` definition.') + end # Only return the encrypted attribute if it's available and encrypted_attribute_only is true. define_method(non_encrypted_attribute) do return send(encrypted_attribute) if options[:encrypted_attribute_only] diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 300c5314..f27b7377 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -4,6 +4,7 @@ require 'json' require_relative 'encrypted_model' +require_relative 'attribute_proxy' require_relative 'rails/configurable' require_relative 'rails/errors' require_relative 'rails/serializers/json_serializer' From eabde1c3955aca55f911aabce5d76dbf5e9c8c26 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Wed, 21 Nov 2018 11:00:21 +0000 Subject: [PATCH 042/143] Add documentation for upgrading from 0.6 to 0.7 --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 1e36aac2..d9aee2ee 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,18 @@ In order to use this method for an attribute you need to add the following row i vault_attribute_proxy :attribute, :attribute_ciphertext, encrypted_attribute_only: true ``` +Upgrading to 0.7 from 0.6 +------------------------- + +Version 0.6 targets rails 4.x and 0.7 targets rails 5.x. There are breaking changes between the two versions too, so upgrading isn't as smooth as it could be. + +1. You no longer need to `include Vault::AttributeProxy` to get `vault_attribute_proxy` as it is part of `Vault::EncryptedModel` now. In 0.7.0 the `Vault::AttributeProxy` module isn't part of the gem, but from 0.7.1+ it exists just to emit a deprecation warning reminding you to stop including it. + + **If you do nothing** your app will still work properly in 0.7.1+, but you'll get annoying messages. + +2. The position of the `type` parameter has changed from `vault_attribute_proxy` in 0.6 to `vault_attribute` in 0.7. In 0.7 if you have a `type` option on `vault_attribute_proxy` it will emit a deprecation warning reminding you to move the definition onto `vault_attribute`. + + **If you do nothing** your app will likely break because `fc-vault-rails` will assume your `vault_attribute` is just a string and if you had `type` on `vault_attribute_proxy` it's likely not. Development ----------- From beb5a4e24de9176cb0067c4954f2f2c88e74b1ba Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Wed, 21 Nov 2018 11:03:26 +0000 Subject: [PATCH 043/143] Update changelog with latest features in 0.7 --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 876532a5..cb36651f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased NEW FEATURES +- Support for batch encryption/decryption via `Vault::Rails.batch_encrypt` + and `Vault::Rails.batch_decrypt` methods. - Introduce deprecation warnings for the breaking changes between 0.6 and 0.7. This includes adding back `Vault::AttributeProxy` as an empty module that generates a deprecation warning. @@ -10,6 +12,7 @@ NEW FEATURES BUG FIXES - Actually persist encrypted attributes when using `vault_persist_before_save!` in rails 5.2 +- Support lazy loading of `nil` values. ## v0.7.0 (October 24, 2018) From 6e19b9902e50c24e5817ecce0c3668eb547909ee Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Wed, 21 Nov 2018 11:04:10 +0000 Subject: [PATCH 044/143] Bump to 0.7.1 in preparation for release This also involves updating the gemfiles used by appraisal to run the tests against multiple rails versions to get the new 0.7.1 version. --- CHANGELOG.md | 2 +- gemfiles/rails_4.2.gemfile.lock | 4 ++-- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb36651f..c9558024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Vault Rails Changelog -## Unreleased +## v0.7.1 (November 21, 2018) NEW FEATURES - Support for batch encryption/decryption via `Vault::Rails.batch_encrypt` diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index d8c9cec7..5e563a75 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.0) + fc-vault-rails (0.7.1) activerecord (>= 4.2.8, < 6.0) vault (~> 0.7) @@ -148,4 +148,4 @@ DEPENDENCIES wwtd BUNDLED WITH - 1.16.2 + 1.16.6 diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 86b5c9b1..b004632c 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.0) + fc-vault-rails (0.7.1) activerecord (>= 4.2.8, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index b1e8c9af..4ddebe0a 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.0) + fc-vault-rails (0.7.1) activerecord (>= 4.2.8, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 86a01d6c..e17c9d17 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.0) + fc-vault-rails (0.7.1) activerecord (>= 4.2.8, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index b6a47333..b89ca6e0 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.0" + VERSION = "0.7.1" end end From 1823cc01bf018a07d3ffb16e8ef2051d336e7eeb Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 10:11:20 +0000 Subject: [PATCH 045/143] Actually drop support for rails 4.x When we initially created version 0.7 of the gem to use the activerecord attributes API, our goal was to create a unified version that worked with rails 4.x and 5.x. We ultimately failed in that because the attribute API is private in rails 4.x and does not have the exact same behaviour as it does in rails 5.x. We decided that version 0.6 of the gem would support rails 4.x and version 0.7 would support rails 5.x. We didn't actually make this choice explicit though, and so now we do. This allows us to use rails 5.x features properly. --- .travis.yml | 9 -- Appraisals | 3 - CHANGELOG.md | 5 ++ fc-vault-rails.gemspec | 4 +- gemfiles/rails_4.2.gemfile | 7 -- gemfiles/rails_4.2.gemfile.lock | 151 -------------------------------- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- 9 files changed, 10 insertions(+), 175 deletions(-) delete mode 100644 gemfiles/rails_4.2.gemfile delete mode 100644 gemfiles/rails_4.2.gemfile.lock diff --git a/.travis.yml b/.travis.yml index 29f27020..3d78e5b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ sudo: false gemfile: - Gemfile - - gemfiles/rails_4.2.gemfile - gemfiles/rails_5.0.gemfile - gemfiles/rails_5.1.gemfile - gemfiles/rails_5.2.gemfile @@ -27,14 +26,6 @@ rvm: - 2.4.4 - 2.5.1 -matrix: - exclude: - # Json 1.8.3 cannot build json gem dependency any longer with Ruby 2.4 - - rvm: 2.4.4 - gemfile: gemfiles/rails_4.2.gemfile - - rvm: 2.5.1 - gemfile: gemfiles/rails_4.2.gemfile - before_script: - bundle exec rake app:db:create - bundle exec rake app:db:schema:load diff --git a/Appraisals b/Appraisals index e91a0573..c91f5abc 100644 --- a/Appraisals +++ b/Appraisals @@ -1,6 +1,3 @@ -appraise "rails-4.2" do - gem "rails", "~> 4.2.8" -end appraise "rails-5.0" do gem "rails", "~> 5.0.0" end diff --git a/CHANGELOG.md b/CHANGELOG.md index c9558024..01ee3707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Vault Rails Changelog +## Unreleased + +BREAKING CHANGES +- Actually drop support for rails 4.x, we should have done this in 0.7.0 + ## v0.7.1 (November 21, 2018) NEW FEATURES diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 7b215d01..2bc6aeb9 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", [">= 4.2.8", "< 6.0"] + s.add_dependency "activerecord", [">= 5.0.0", "< 6.0"] s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", [">= 4.2.8", "< 6.0"] + s.add_development_dependency "rails", [">= 5.0.0", "< 6.0"] s.add_development_dependency "byebug" s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile deleted file mode 100644 index 056f1294..00000000 --- a/gemfiles/rails_4.2.gemfile +++ /dev/null @@ -1,7 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "rails", "~> 4.2.8" - -gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock deleted file mode 100644 index 5e563a75..00000000 --- a/gemfiles/rails_4.2.gemfile.lock +++ /dev/null @@ -1,151 +0,0 @@ -PATH - remote: .. - specs: - fc-vault-rails (0.7.1) - activerecord (>= 4.2.8, < 6.0) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.2.10) - actionpack (= 4.2.10) - actionview (= 4.2.10) - activejob (= 4.2.10) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.10) - actionview (= 4.2.10) - activesupport (= 4.2.10) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.10) - activesupport (= 4.2.10) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (4.2.10) - activesupport (= 4.2.10) - globalid (>= 0.3.0) - activemodel (4.2.10) - activesupport (= 4.2.10) - builder (~> 3.1) - activerecord (4.2.10) - activemodel (= 4.2.10) - activesupport (= 4.2.10) - arel (~> 6.0) - activesupport (4.2.10) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - arel (6.0.4) - aws-sigv4 (1.0.3) - builder (3.2.3) - byebug (10.0.2) - coderay (1.1.1) - concurrent-ruby (1.0.5) - crass (1.0.4) - diff-lcs (1.3) - erubis (2.7.0) - globalid (0.4.1) - activesupport (>= 4.2.0) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - loofah (2.2.2) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - method_source (0.8.2) - mini_mime (1.0.1) - mini_portile2 (2.3.0) - minitest (5.11.3) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - pry (0.10.4) - coderay (~> 1.1.0) - method_source (~> 0.8.1) - slop (~> 3.4) - rack (1.6.10) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.10) - actionmailer (= 4.2.10) - actionpack (= 4.2.10) - actionview (= 4.2.10) - activejob (= 4.2.10) - activemodel (= 4.2.10) - activerecord (= 4.2.10) - activesupport (= 4.2.10) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.10) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.9) - activesupport (>= 4.2.0, < 5.0) - nokogiri (~> 1.6) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (4.2.10) - actionpack (= 4.2.10) - activesupport (= 4.2.10) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (10.5.0) - rspec (3.5.0) - rspec-core (~> 3.5.0) - rspec-expectations (~> 3.5.0) - rspec-mocks (~> 3.5.0) - rspec-core (3.5.2) - rspec-support (~> 3.5.0) - rspec-expectations (3.5.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-mocks (3.5.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.5.0) - rspec-support (3.5.0) - slop (3.6.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.3.13) - thor (0.19.4) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - vault (0.12.0) - aws-sigv4 - wwtd (1.3.0) - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - byebug - fc-vault-rails! - pry - rails (~> 4.2.8) - rake (~> 10.0) - rspec (~> 3.2) - sqlite3 - wwtd - -BUNDLED WITH - 1.16.6 diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index b004632c..15fc8210 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (0.7.1) - activerecord (>= 4.2.8, < 6.0) + activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) GEM diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 4ddebe0a..1a124516 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (0.7.1) - activerecord (>= 4.2.8, < 6.0) + activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) GEM diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index e17c9d17..d77e7452 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (0.7.1) - activerecord (>= 4.2.8, < 6.0) + activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) GEM From aa4a1c2461f7431f6054461f53ae7fae68e960af Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 15:33:47 +0000 Subject: [PATCH 046/143] Tidy up serializer specs to remove extra space --- spec/unit/rails/serializers/date_serializer_spec.rb | 4 ++-- spec/unit/rails/serializers/float_serializer_spec.rb | 12 ++++++------ .../rails/serializers/integer_serializer_spec.rb | 12 ++++++------ 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/spec/unit/rails/serializers/date_serializer_spec.rb b/spec/unit/rails/serializers/date_serializer_spec.rb index 333d8efb..1aea7a70 100644 --- a/spec/unit/rails/serializers/date_serializer_spec.rb +++ b/spec/unit/rails/serializers/date_serializer_spec.rb @@ -2,11 +2,11 @@ describe Vault::Rails::Serializers::DateSerializer do it 'encodes values to strings' do - expect(subject.encode(Date.new(1999, 1, 1))). to eq '1999-01-01' + expect(subject.encode(Date.new(1999, 1, 1))).to eq '1999-01-01' end it 'decodes values from strings' do - expect(subject.decode('1999-12-31')). to eq Date.new(1999, 12, 31) + expect(subject.decode('1999-12-31')).to eq Date.new(1999, 12, 31) end end diff --git a/spec/unit/rails/serializers/float_serializer_spec.rb b/spec/unit/rails/serializers/float_serializer_spec.rb index 0ec2b5bc..bb2108e7 100644 --- a/spec/unit/rails/serializers/float_serializer_spec.rb +++ b/spec/unit/rails/serializers/float_serializer_spec.rb @@ -2,14 +2,14 @@ describe Vault::Rails::Serializers::FloatSerializer do it 'encodes values to strings' do - expect(subject.encode(1.0)). to eq '1.0' - expect(subject.encode(42.00001)). to eq '42.00001' - expect(subject.encode(435345.40035)). to eq '435345.40035' + expect(subject.encode(1.0)).to eq '1.0' + expect(subject.encode(42.00001)).to eq '42.00001' + expect(subject.encode(435345.40035)).to eq '435345.40035' end it 'decodes values from strings' do - expect(subject.decode('1.0')). to eq 1.0 - expect(subject.decode('42')). to eq 42.0 - expect(subject.decode('435345.40035')). to eq 435345.40035 + expect(subject.decode('1.0')).to eq 1.0 + expect(subject.decode('42')).to eq 42.0 + expect(subject.decode('435345.40035')).to eq 435345.40035 end end diff --git a/spec/unit/rails/serializers/integer_serializer_spec.rb b/spec/unit/rails/serializers/integer_serializer_spec.rb index 1ddab39f..1188e471 100644 --- a/spec/unit/rails/serializers/integer_serializer_spec.rb +++ b/spec/unit/rails/serializers/integer_serializer_spec.rb @@ -2,14 +2,14 @@ describe Vault::Rails::Serializers::IntegerSerializer do it 'encodes values to strings' do - expect(subject.encode(1)). to eq '1' - expect(subject.encode(42)). to eq '42' - expect(subject.encode(23425)). to eq '23425' + expect(subject.encode(1)).to eq '1' + expect(subject.encode(42)).to eq '42' + expect(subject.encode(23425)).to eq '23425' end it 'decodes values from strings' do - expect(subject.decode('1')). to eq 1 - expect(subject.decode('42')). to eq 42 - expect(subject.decode('23425')). to eq 23425 + expect(subject.decode('1')).to eq 1 + expect(subject.decode('42')).to eq 42 + expect(subject.decode('23425')).to eq 23425 end end From 64bb172cb6e50cd45277e17bff5c885be8bbbb7d Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 15:35:32 +0000 Subject: [PATCH 047/143] Introduce spec for JSON serializer --- spec/unit/rails/serializers/json_serializer_spec.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 spec/unit/rails/serializers/json_serializer_spec.rb diff --git a/spec/unit/rails/serializers/json_serializer_spec.rb b/spec/unit/rails/serializers/json_serializer_spec.rb new file mode 100644 index 00000000..fc7f6da8 --- /dev/null +++ b/spec/unit/rails/serializers/json_serializer_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::JSONSerializer do + it 'encodes values to strings' do + expect(subject.encode({"foo" => "bar", "baz" => 1})).to eq '{"foo":"bar","baz":1}' + end + + it 'decodes values from strings' do + expect(subject.decode('{"foo":"bar","baz":1}')).to eq({"foo" => "bar", "baz" => 1}) + end +end From a373093f7aeaefc60169fdb46be4163cc6d6c8aa Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 11:33:52 +0000 Subject: [PATCH 048/143] Introduce serializers for `time` and `datetime` These might seem redundant, but we'll shortly be introducing the ability to detect the serializer from the type, and these are both types that rails provides by default for attributes so it makes sense to support them. --- CHANGELOG.md | 3 +++ lib/vault/rails.rb | 6 ++++- .../rails/serializers/date_time_serializer.rb | 17 ++++++++++++++ .../rails/serializers/time_serializer.rb | 23 +++++++++++++++++++ .../rails/serializers/date_serializer_spec.rb | 1 - .../serializers/date_time_serializer_spec.rb | 11 +++++++++ .../rails/serializers/time_serializer_spec.rb | 11 +++++++++ 7 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 lib/vault/rails/serializers/date_time_serializer.rb create mode 100644 lib/vault/rails/serializers/time_serializer.rb create mode 100644 spec/unit/rails/serializers/date_time_serializer_spec.rb create mode 100644 spec/unit/rails/serializers/time_serializer_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 01ee3707..03d33356 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +NEW FEATURES +- New serializers for `time` and `datetime` + BREAKING CHANGES - Actually drop support for rails 4.x, we should have done this in 0.7.0 diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index f27b7377..afc3137f 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -11,6 +11,8 @@ require_relative 'rails/serializers/date_serializer' require_relative 'rails/serializers/integer_serializer' require_relative 'rails/serializers/float_serializer' +require_relative 'rails/serializers/time_serializer' +require_relative 'rails/serializers/date_time_serializer' require_relative 'rails/version' module Vault @@ -22,7 +24,9 @@ module Rails json: Vault::Rails::Serializers::JSONSerializer, date: Vault::Rails::Serializers::DateSerializer, integer: Vault::Rails::Serializers::IntegerSerializer, - float: Vault::Rails::Serializers::FloatSerializer + float: Vault::Rails::Serializers::FloatSerializer, + time: Vault::Rails::Serializers::TimeSerializer, + datetime: Vault::Rails::Serializers::DateTimeSerializer }.freeze # The warning string to print when running in development mode. diff --git a/lib/vault/rails/serializers/date_time_serializer.rb b/lib/vault/rails/serializers/date_time_serializer.rb new file mode 100644 index 00000000..e3187165 --- /dev/null +++ b/lib/vault/rails/serializers/date_time_serializer.rb @@ -0,0 +1,17 @@ +module Vault + module Rails + module Serializers + # Converts datetime objects to and from ISO 8601 format with 3 + # fractional seconds + module DateTimeSerializer + include TimeSerializer + module_function :encode, :decode + + def decode(raw) + time = super + time.present? ? time.to_datetime : time + end + end + end + end +end diff --git a/lib/vault/rails/serializers/time_serializer.rb b/lib/vault/rails/serializers/time_serializer.rb new file mode 100644 index 00000000..5297d278 --- /dev/null +++ b/lib/vault/rails/serializers/time_serializer.rb @@ -0,0 +1,23 @@ +module Vault + module Rails + module Serializers + # Converts time objects to and from ISO 8601 format with 3 + # fractional seconds + module TimeSerializer + module_function + + def encode(raw) + return nil if raw.blank? + + raw = Time.parse(raw) if raw.is_a? String + raw.iso8601(3) + end + + def decode(raw) + return nil if raw.blank? + Time.iso8601(raw) + end + end + end + end +end diff --git a/spec/unit/rails/serializers/date_serializer_spec.rb b/spec/unit/rails/serializers/date_serializer_spec.rb index 1aea7a70..e58369b1 100644 --- a/spec/unit/rails/serializers/date_serializer_spec.rb +++ b/spec/unit/rails/serializers/date_serializer_spec.rb @@ -9,4 +9,3 @@ expect(subject.decode('1999-12-31')).to eq Date.new(1999, 12, 31) end end - diff --git a/spec/unit/rails/serializers/date_time_serializer_spec.rb b/spec/unit/rails/serializers/date_time_serializer_spec.rb new file mode 100644 index 00000000..44e64088 --- /dev/null +++ b/spec/unit/rails/serializers/date_time_serializer_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::DateTimeSerializer do + it 'encodes values to strings' do + expect(subject.encode(DateTime.new(1999, 1, 1, 10, 11, 12.134, '0'))).to eq '1999-01-01T10:11:12.134+00:00' + end + + it 'decodes values from strings' do + expect(subject.decode('1999-12-31T20:21:22.234+00:00')).to eq DateTime.new(1999, 12, 31, 20, 21, 22.234, '0') + end +end diff --git a/spec/unit/rails/serializers/time_serializer_spec.rb b/spec/unit/rails/serializers/time_serializer_spec.rb new file mode 100644 index 00000000..47b74d6e --- /dev/null +++ b/spec/unit/rails/serializers/time_serializer_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::TimeSerializer do + it 'encodes values to strings' do + expect(subject.encode(Time.utc(1999, 1, 1, 10, 11, 12, 134000))).to eq '1999-01-01T10:11:12.134Z' + end + + it 'decodes values from strings' do + expect(subject.decode('1999-12-31T20:21:22.234Z')).to eq Time.utc(1999, 12, 31, 20, 21, 22, 234000) + end +end From 49e334066222d5c4513627f1592d60c4b9821924 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 12:02:16 +0000 Subject: [PATCH 049/143] Use `ActiveRecord::Type.lookup` to find type classes Our original strategy of looking for `ActiveRecord::Type::` works for all the default rails types, but it doesn't allow us to make use of any custom types that our database adapter might introduce. In rails 5+ `ActiveRecord::Type.lookup` was introduced to let us find a type implementation that has previously been registered. This means we can use the same set of types as our database columns, but also any custom types that we might register ourselves (like a Money class or whatever). This change is why we finally dropped support for rail 4.x as it does not have this functionality so it's impossible to know what other custom types are available at runtime. --- CHANGELOG.md | 2 ++ lib/vault/encrypted_model.rb | 16 +++++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03d33356..e9c0b3c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ NEW FEATURES - New serializers for `time` and `datetime` +- Allow symbol values for `type` to find any type class registered with + `ActiveRecord::Type`, not just the constants defined under it BREAKING CHANGES - Actually drop support for rails 4.x, we should have done this in 0.7.0 diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 35f78033..4766bcf1 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -62,16 +62,18 @@ def vault_attribute(attribute_name, options = {}) serializer.define_singleton_method(:decode, &options[:decode]) end - attribute_type = options.fetch(:type, :value) + attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) if attribute_type.is_a?(Symbol) - constant_name = attribute_type.to_s.camelize - - unless ActiveRecord::Type.const_defined?(constant_name) - raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" + begin + attribute_type = ActiveRecord::Type.lookup(attribute_type) + rescue ArgumentError => e + if e.message =~ /Unknown type / + raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" + else + raise + end end - - attribute_type = ActiveRecord::Type.const_get(constant_name).new end # Attribute API From 7d49ad3deba2be180a555536dbcbcfef2672283c Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 11:57:28 +0000 Subject: [PATCH 050/143] Detect serializer based on type for vault_attribute For most cases, if we have specified a `type` option on `vault_attribute` we will also need a serializer to deal with sending the value to vault as a string, and converting it back again. For most cases, we can also detect the serializer based on the type and this means the author of the `vault_attribute` definitions doesn't need to worry about it. --- CHANGELOG.md | 2 + lib/vault/encrypted_model.rb | 76 +++++++++++++++++---------- spec/dummy/app/models/typed_person.rb | 33 ++++++++++++ spec/unit/encrypted_model_spec.rb | 45 +++++++++++++++- 4 files changed, 126 insertions(+), 30 deletions(-) create mode 100644 spec/dummy/app/models/typed_person.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index e9c0b3c8..6eb7f484 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ NEW FEATURES - New serializers for `time` and `datetime` - Allow symbol values for `type` to find any type class registered with `ActiveRecord::Type`, not just the constants defined under it +- If `type` is specified but serialization options aren't then attempt to + detect a default serializer based on the type. BREAKING CHANGES - Actually drop support for rails 4.x, we should have done this in 0.7.0 diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 4766bcf1..bba6f566 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -1,4 +1,6 @@ require "active_support/concern" +require "active_record" +require "active_record/type" module Vault module EncryptedModel @@ -46,35 +48,7 @@ def vault_attribute(attribute_name, options = {}) # Sanity check options! _vault_validate_options!(options) - # Get the serializer if one was given. - serializer = options[:serialize] - - # Unless a class or module was given, construct our serializer. (Slass - # is a subset of Module). - if serializer && !serializer.is_a?(Module) - serializer = Vault::Rails.serializer_for(serializer) - end - - # See if custom encoding or decoding options were given. - if options[:encode] && options[:decode] - serializer = Class.new - serializer.define_singleton_method(:encode, &options[:encode]) - serializer.define_singleton_method(:decode, &options[:decode]) - end - - attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) - - if attribute_type.is_a?(Symbol) - begin - attribute_type = ActiveRecord::Type.lookup(attribute_type) - rescue ArgumentError => e - if e.message =~ /Unknown type / - raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" - else - raise - end - end - end + attribute_type = _vault_fetch_attribute_type(options) # Attribute API attribute(attribute_name, attribute_type) @@ -103,6 +77,8 @@ def vault_attribute(attribute_name, options = {}) cast_value end + serializer = _vault_fetch_serializer(options, attribute_type) + # Make a note of this attribute so we can use it in the future (maybe). __vault_attributes[attribute_name.to_sym] = { key: key, @@ -186,6 +162,48 @@ def _vault_validate_options!(options) end end + def _vault_fetch_attribute_type(options) + attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) + + if attribute_type.is_a?(Symbol) + ActiveRecord::Type.lookup(attribute_type) + else + attribute_type + end + rescue ArgumentError => e + if e.message =~ /Unknown type / + raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" + else + raise + end + end + + def _vault_fetch_serializer(options, attribute_type) + if options[:serialize] + serializer = options[:serialize] + + # Unless a class or module was given, construct our serializer. (Slass + # is a subset of Module). + if serializer && !serializer.is_a?(Module) + Vault::Rails.serializer_for(serializer) + else + serializer + end + elsif options[:encode] && options[:decode] + # See if custom encoding or decoding options were given. + Class.new do + define_singleton_method(:encode, &options[:encode]) + define_singleton_method(:decode, &options[:decode]) + end + elsif attribute_type.is_a?(ActiveRecord::Type::Value) && attribute_type.type.present? + begin + Vault::Rails.serializer_for(attribute_type.type) + rescue Vault::Rails::Serializers::UnknownSerializerError + nil + end + end + end + def vault_lazy_decrypt? !!@vault_lazy_decrypt end diff --git a/spec/dummy/app/models/typed_person.rb b/spec/dummy/app/models/typed_person.rb new file mode 100644 index 00000000..6bfcaa9d --- /dev/null +++ b/spec/dummy/app/models/typed_person.rb @@ -0,0 +1,33 @@ +require "binary_serializer" + +class TypedPerson < ActiveRecord::Base + include Vault::EncryptedModel + + self.table_name = "people" + + # types with default serializers + vault_attribute :integer_data, type: :integer + + vault_attribute :float_data, type: :float + + vault_attribute :time_data, type: :time + + vault_attribute :date_data, encrypted_column: :state_encrypted, type: :date + + vault_attribute :date_time_data, encrypted_column: :county_encrypted, type: :datetime + + # types that do not have default serializers + vault_attribute :decimal_data, encrypted_column: :ssn_encrypted, type: :decimal + + vault_attribute :string_data, encrypted_column: :cc_encrypted, type: :string + + vault_attribute :text_data, encrypted_column: :address_encrypted, type: :text + + # overriding the default serializer + vault_attribute :custom_date_time_data, type: :datetime, serialize: :date + + vault_attribute :custom_float_data, + type: :datetime, + encode: ->(float_value) { float_value.round.to_s }, + decode: ->(decrypted_value) { decrypted_value.to_f.round } +end diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 78ac780f..283373d4 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -84,7 +84,50 @@ it 'raises an error with unknown attribute type' do expect do Person.vault_attribute :unrecognized_attr, type: :unrecognized - end.to raise_error RuntimeError, /Unrecognized/ + end.to raise_error RuntimeError, /Unrecognized attribute type/ + end + + it 'defines a default serialzer if it has one for the type' do + time_data_vault_options = TypedPerson.__vault_attributes[:time_data] + expect(time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::TimeSerializer + + integer_data_vault_options = TypedPerson.__vault_attributes[:integer_data] + expect(integer_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::IntegerSerializer + + float_data_vault_options = TypedPerson.__vault_attributes[:float_data] + expect(float_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::FloatSerializer + + date_data_vault_options = TypedPerson.__vault_attributes[:date_data] + expect(date_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateSerializer + + date_time_data_vault_options = TypedPerson.__vault_attributes[:date_time_data] + expect(date_time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateTimeSerializer + end + + it 'does not add a default serialzer if it does not have one for the type' do + string_data_vault_options = TypedPerson.__vault_attributes[:string_data] + expect(string_data_vault_options[:serializer]).to be_nil + + decimal_data_vault_options = TypedPerson.__vault_attributes[:decimal_data] + expect(decimal_data_vault_options[:serializer]).to be_nil + + text_data_vault_options = TypedPerson.__vault_attributes[:text_data] + expect(text_data_vault_options[:serializer]).to be_nil + end + + it 'allows overriding the default serialzer via the `serializer` option' do + custom_date_time_data_vault_options = TypedPerson.__vault_attributes[:custom_date_time_data] + expect(custom_date_time_data_vault_options[:serializer]).not_to eq Vault::Rails::Serializers::DateTimeSerializer + expect(custom_date_time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateSerializer + end + + it 'allows overriding the default serializer via the `encode` and `decode` options' do + custom_float_data_vault_options = TypedPerson.__vault_attributes[:custom_float_data] + expect(custom_float_data_vault_options[:serializer]).not_to eq Vault::Rails::Serializers::FloatSerializer + # we can't reasonably assert on the value of serializer, so we'll + # check what it does instead + expect(custom_float_data_vault_options[:serializer].encode(1.5)).to eq '2' + expect(custom_float_data_vault_options[:serializer].decode('1.5')).to eq 2 end end end From 5daba095ddf8cfd6e5eec7e1cd898462b425f701 Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 12:34:03 +0000 Subject: [PATCH 051/143] Add a serializer for IPAddr objects In postgresql there are inet and cidr types for columns that are handled by the `ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Cidr` and `ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Inet` type classes. This means we get ruby `IPAddr` objects as the value for our attributes when we read them out of the database with `ActiveRecord`. We want to support these column types out of the box so we've introduced a serializer for `IPAddr` (based heavily on the implementation of the type class) and set this serializer as the default for the `inet` and `cidr` types. We also want to support `IPAddr` serialization for other column types that might use `IPAddr` so we've also added it as the serializer for `ipaddr`, so that other database adapters are not beholden to the names postgresql uses. --- CHANGELOG.md | 2 ++ lib/vault/rails.rb | 6 +++++- .../rails/serializers/ipaddr_serializer.rb | 21 +++++++++++++++++++ .../serializers/ipaddr_serializer_spec.rb | 19 +++++++++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 lib/vault/rails/serializers/ipaddr_serializer.rb create mode 100644 spec/unit/rails/serializers/ipaddr_serializer_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 6eb7f484..de5a33a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ NEW FEATURES `ActiveRecord::Type`, not just the constants defined under it - If `type` is specified but serialization options aren't then attempt to detect a default serializer based on the type. +- New serializer for `ipaddr`, which acts as a default for `inet` and + `cidr` too. BREAKING CHANGES - Actually drop support for rails 4.x, we should have done this in 0.7.0 diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index afc3137f..5fbfa563 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -13,6 +13,7 @@ require_relative 'rails/serializers/float_serializer' require_relative 'rails/serializers/time_serializer' require_relative 'rails/serializers/date_time_serializer' +require_relative 'rails/serializers/ipaddr_serializer' require_relative 'rails/version' module Vault @@ -26,7 +27,10 @@ module Rails integer: Vault::Rails::Serializers::IntegerSerializer, float: Vault::Rails::Serializers::FloatSerializer, time: Vault::Rails::Serializers::TimeSerializer, - datetime: Vault::Rails::Serializers::DateTimeSerializer + datetime: Vault::Rails::Serializers::DateTimeSerializer, + ipaddr: Vault::Rails::Serializers::IPAddrSerializer, + inet: Vault::Rails::Serializers::IPAddrSerializer, + cidr: Vault::Rails::Serializers::IPAddrSerializer }.freeze # The warning string to print when running in development mode. diff --git a/lib/vault/rails/serializers/ipaddr_serializer.rb b/lib/vault/rails/serializers/ipaddr_serializer.rb new file mode 100644 index 00000000..1d4a89de --- /dev/null +++ b/lib/vault/rails/serializers/ipaddr_serializer.rb @@ -0,0 +1,21 @@ +module Vault + module Rails + module Serializers + # Converts IPAddr objects to and from strings + module IPAddrSerializer + module_function + + def encode(ip_addr) + return nil if ip_addr.blank? + + "#{ip_addr}/#{ip_addr.instance_variable_get(:@mask_addr).to_s(2).count('1')}" + end + + def decode(string) + return nil if string.blank? + IPAddr.new(string) + end + end + end + end +end diff --git a/spec/unit/rails/serializers/ipaddr_serializer_spec.rb b/spec/unit/rails/serializers/ipaddr_serializer_spec.rb new file mode 100644 index 00000000..996ba50d --- /dev/null +++ b/spec/unit/rails/serializers/ipaddr_serializer_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::IPAddrSerializer do + it 'encodes values to strings for IP4 addresses' do + expect(subject.encode(IPAddr.new('192.168.1.255/32'))).to eq '192.168.1.255/32' + end + + it 'decodes values from strings for IP4 addresses' do + expect(subject.decode('192.168.1.1/1')).to eq IPAddr.new('192.168.1.1/1') + end + + it 'encodes values to strings for IP6 addresses' do + expect(subject.encode(IPAddr.new('fd12:3456:789a:1::ffff/128'))).to eq 'fd12:3456:789a:1::ffff/128' + end + + it 'decodes values from strings for IP6 addresses' do + expect(subject.decode('fd12:3456:789a:1::1/1')).to eq IPAddr.new('fd12:3456:789a:1::1/1') + end +end From cbd9a576d2dfebe3b0e30463580393b9c2d1b66d Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 16:31:37 +0000 Subject: [PATCH 052/143] Include 0.6.4->0.6.6 in changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index de5a33a2..2e315b3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,26 @@ BREAKING CHANGES - type information is now specified on `vault_attribute` definitions instead of the `vault_attribute_proxy` definitions. +## v0.6.6 (December 3, 2018) + +NEW FEATURES +- New serializers for `time` and `datetime` +- New serializer for `ipaddr`. + +## v0.6.5 (November 28, 2018) + +IMPROVEMENTS +- Add `EncryptedModel.vault_persist_all` for encrypting and saving one attribute of multiple records with just one call to Vault +- Add `EncryptedModel.vault_load_all` for decrypting and loading one attribute of multiple records with just one call to Vault +- Allow blank values like `nil` and empty string as input to batch encryption and decryption + +## v0.6.4 (November 13, 2018) + +NEW FEATURES +- Allow batch encryption and decryption. + Now there is an option to encrypt or decrypt multiple strings at once. + All items to be encrypted/decrypted should use the same path, key and client. + ## v0.6.3 (October 31, 2018) NEW FEATURES From 5197e637133c7c87a9cdb1394bfaf589e89fc06c Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 3 Dec 2018 16:31:54 +0000 Subject: [PATCH 053/143] Create v0.7.2 --- CHANGELOG.md | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e315b3b..ca3c7d64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Vault Rails Changelog -## Unreleased +## 0.7.2 (December 3, 2018) NEW FEATURES - New serializers for `time` and `datetime` diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 15fc8210..bffa6c90 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.1) + fc-vault-rails (0.7.2) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 1a124516..50f31c63 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.1) + fc-vault-rails (0.7.2) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index d77e7452..ddae10a6 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.1) + fc-vault-rails (0.7.2) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index b89ca6e0..90c6133c 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.1" + VERSION = "0.7.2" end end From 662b0c5b44e0bfd9b708b1ae2210630462df0ee4 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Mon, 19 Nov 2018 18:14:20 +0200 Subject: [PATCH 054/143] Handle blank values in batch encryption and decryption This is a cherry-pick and squash of the following commits from the 0.6 branch which add handling of blank values in the array of plaintexts and handling of the entire array being full of blank values: * f631432b5393e233b01bd7812b6ca52addb6c222 - Handle blank values in batch encryption and decryption * da08d60895b5067f1d768a96f216154937a08c88 - Batch encryption - handle arrays with only blank values We squashed it because the second is really a bug fix of the first so it makes sense to have a single fixed commit when we have that option. --- CHANGELOG.md | 6 ++ lib/vault/rails.rb | 28 +++++++--- spec/unit/rails_spec.rb | 119 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca3c7d64..0ff0c463 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Vault Rails Changelog +## Unreleased + +BUG FIXES +- Allow blank values like `nil` and empty string as input to batch encryption and decryption (forward ported from 0.6.5) +- Handle the case when plaintexts/ciphertexts parameter of #vault_batch_encrypt/#vault_batch_decrypt is an array with only blank values (forward ported from 0.6.7) + ## 0.7.2 (December 3, 2018) NEW FEATURES diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 5fbfa563..b09d54d1 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -260,6 +260,10 @@ def vault_encrypt(path, key, plaintext, client, convergent) def vault_batch_encrypt(path, key, plaintexts, client) return [] if plaintexts.empty? + # Only present values can be encrypted by Vault. Empty values should be returned as they are. + non_empty_plaintexts = plaintexts.select { |plaintext| plaintext.present? } + return plaintexts if non_empty_plaintexts.empty? # nothing to encrypt + route = File.join(path, 'encrypt', key) options = { @@ -267,7 +271,7 @@ def vault_batch_encrypt(path, key, plaintexts, client) derived: true } - batch_input = plaintexts.map do |plaintext| + batch_input = non_empty_plaintexts.map do |plaintext| { context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), plaintext: Base64.strict_encode64(plaintext) @@ -277,7 +281,11 @@ def vault_batch_encrypt(path, key, plaintexts, client) options.merge!(batch_input: batch_input) secret = client.logical.write(route, options) - secret.data[:batch_results].map { |result| result[:ciphertext] } + vault_results = secret.data[:batch_results].map { |result| result[:ciphertext] } + + plaintexts.map do |plaintext| + plaintext.present? ? vault_results.shift : plaintext + end end # Perform decryption using Vault. This will raise exceptions if Vault is @@ -302,20 +310,26 @@ def vault_decrypt(path, key, ciphertext, client, convergent) def vault_batch_decrypt(path, key, ciphertexts, client) return [] if ciphertexts.empty? - route = File.join(path, 'decrypt', key) + # Only present values can be decrypted by Vault. Empty values should be returned as they are. + non_empty_ciphertexts = ciphertexts.select { |ciphertext| ciphertext.present? } + return ciphertexts if non_empty_ciphertexts.empty? + route = File.join(path, 'decrypt', key) - batch_input = ciphertexts.map do |ciphertext| + batch_input = non_empty_ciphertexts.map do |ciphertext| { context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), ciphertext: ciphertext } end - options = { batch_input: batch_input } secret = client.logical.write(route, options) - secret.data[:batch_results].map { |result| Base64.strict_decode64(result[:plaintext]) } + vault_results = secret.data[:batch_results].map { |result| Base64.strict_decode64(result[:plaintext]) } + + ciphertexts.map do |ciphertext| + ciphertext.present? ? vault_results.shift : ciphertext + end end # The symmetric key for the given params. @@ -328,7 +342,7 @@ def memory_key_for(path, key) # newly encoded string. # @return [String] def force_encoding(str) - str.force_encoding(Vault::Rails.encoding).encode(Vault::Rails.encoding) + str.blank? ? str : str.force_encoding(Vault::Rails.encoding).encode(Vault::Rails.encoding) end private diff --git a/spec/unit/rails_spec.rb b/spec/unit/rails_spec.rb index bc576315..a991b230 100644 --- a/spec/unit/rails_spec.rb +++ b/spec/unit/rails_spec.rb @@ -156,6 +156,65 @@ expect(Vault::Rails.batch_encrypt('path', 'key', ['plaintext1', 'plaintext2'], Vault::Rails.client)).to eq(%w(ciphertext1 ciphertext2)) end + + context 'with only blank values' do + it 'does not make any calls to Vault and just return the plaintexts' do + expect(Vault::Rails.client.logical).not_to receive(:write) + + plaintexts = ['', '', nil, '', nil, nil] + expect(Vault::Rails.batch_encrypt('path', 'key', plaintexts, Vault::Rails.client)).to eq(plaintexts) + end + end + + context 'with presented blank values' do + it 'sends the correct parameters to vault client' do + expected_route = 'path/encrypt/key' + expected_options = { + batch_input: [ + { + plaintext: Base64.strict_encode64('plaintext1'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + plaintext: Base64.strict_encode64('plaintext2'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + convergent_encryption: true, + derived: true + } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.batch_encrypt('path', 'key', ['plaintext1', '', 'plaintext2', '', nil, nil], Vault::Rails.client) + end + + it 'parses the response from vault client correctly and keeps the order of records' do + expected_route = 'path/encrypt/key' + expected_options = { + batch_input: [ + { + plaintext: Base64.strict_encode64('plaintext1'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + plaintext: Base64.strict_encode64('plaintext2'), + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + convergent_encryption: true, + derived: true + } + + allow(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(instance_double('Vault::Secret', data: {:batch_results=>[{:ciphertext=>'ciphertext1'}, {:ciphertext=>'ciphertext2'}]})) + + expect(Vault::Rails.batch_encrypt('path', 'key', ['plaintext1', '', 'plaintext2', '', nil], Vault::Rails.client)).to eq(['ciphertext1', '', 'ciphertext2', '', nil]) + end + end end describe '.batch_decrypt' do @@ -207,5 +266,65 @@ expect(Vault::Rails.batch_decrypt('path', 'key', ['ciphertext1', 'ciphertext2'], Vault::Rails.client)).to eq( %w(plaintext1 plaintext2)) # in that order end + + context 'with only blank values' do + it 'does not make any calls to Vault and just return the ciphertexts' do + expect(Vault::Rails.client.logical).not_to receive(:write) + + ciphertexts = ['', '', nil, '', nil, nil] + + expect(Vault::Rails.batch_decrypt('path', 'key', ciphertexts, Vault::Rails.client)).to eq(ciphertexts) + end + end + + context 'with presented blank values' do + it 'sends the correct parameters to vault client' do + expected_route = 'path/decrypt/key' + expected_options = { + batch_input: [ + { + ciphertext: 'ciphertext1', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + ciphertext: 'ciphertext2', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + } + + expect(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(spy('Vault::Secret')) + + Vault::Rails.batch_decrypt('path', 'key', ['ciphertext1', '', 'ciphertext2', nil, '', ''], Vault::Rails.client) + end + + it 'parses the response from vault client correctly and keeps the order of records' do + expected_route = 'path/decrypt/key' + expected_options = { + batch_input: [ + { + ciphertext: 'ciphertext1', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + ciphertext: 'ciphertext2', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + { + ciphertext: 'ciphertext3', + context: Base64.strict_encode64(Vault::Rails.convergent_encryption_context), + }, + ], + } + + allow(Vault::Rails.client.logical).to receive(:write) + .with(expected_route, expected_options) + .and_return(instance_double('Vault::Secret', data: {batch_results: [{plaintext: 'cGxhaW50ZXh0MQ=='}, {plaintext:'cGxhaW50ZXh0Mg=='}, {plaintext: 'cGxhaW50ZXh0Mw=='}]})) + + expect(Vault::Rails.batch_decrypt('path', 'key', ['ciphertext1', '', nil, 'ciphertext2', '', 'ciphertext3'], Vault::Rails.client)).to eq( ['plaintext1', '', nil, 'plaintext2', '', 'plaintext3']) # in that order + end + end end end From ef90d72a0eccd2b3fcf6534c3e8c2039adc6fdec Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 10 Dec 2018 17:07:11 +0000 Subject: [PATCH 055/143] Include 0.6.7 in the 0.7.x changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ff0c463..2fd871ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,11 @@ BREAKING CHANGES - type information is now specified on `vault_attribute` definitions instead of the `vault_attribute_proxy` definitions. +## v0.6.7 (December 7, 2018) + +BUG FIXES +- Handle the case when plaintexts/ciphertexts parameter of #vault_batch_encrypt/#vault_batch_decrypt is an array with only blank values + ## v0.6.6 (December 3, 2018) NEW FEATURES From 319a0fb2cc2a0c8353c634c30d3dfdc7f2cea3dd Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Mon, 10 Dec 2018 17:10:22 +0000 Subject: [PATCH 056/143] Create 0.7.3 --- CHANGELOG.md | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2fd871ca..837d75c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Vault Rails Changelog -## Unreleased +## 0.7.3 (December 10, 2018) BUG FIXES - Allow blank values like `nil` and empty string as input to batch encryption and decryption (forward ported from 0.6.5) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index bffa6c90..8d44102b 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.2) + fc-vault-rails (0.7.3) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 50f31c63..a9681caf 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.2) + fc-vault-rails (0.7.3) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index ddae10a6..c0922d0b 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.2) + fc-vault-rails (0.7.3) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 90c6133c..40fad252 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.2" + VERSION = "0.7.3" end end From fb25ed1d22a6d81144b881f5c0ab899fcea38d11 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Mon, 19 Nov 2018 16:09:47 +0200 Subject: [PATCH 057/143] Fix typo in #memory_batch_encrypt --- lib/vault/rails.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index b09d54d1..363069ac 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -209,7 +209,7 @@ def memory_encrypt(path, key, plaintext, _client, convergent) # Perform in-memory encryption. This is useful for testing and development. def memory_batch_encrypt(path, key, plaintexts, _client) - plaintexts.map { |plaintext| memory_encrypt(path, key, ciphertext, _client, true) } + plaintexts.map { |plaintext| memory_encrypt(path, key, plaintext, _client, true) } end # Perform in-memory decryption. This is useful for testing and development. From 6369a3897bcdba9044114f7bd7858d60671debd6 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Mon, 19 Nov 2018 16:49:17 +0200 Subject: [PATCH 058/143] Add convergent field #passport_number for LazyPerson --- spec/dummy/app/models/lazy_person.rb | 2 + ...119142920_add_passport_number_to_people.rb | 5 +++ spec/dummy/db/schema.rb | 41 +++++++++---------- 3 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 spec/dummy/db/migrate/20181119142920_add_passport_number_to_people.rb diff --git a/spec/dummy/app/models/lazy_person.rb b/spec/dummy/app/models/lazy_person.rb index 51c6f3b3..8ae1005e 100644 --- a/spec/dummy/app/models/lazy_person.rb +++ b/spec/dummy/app/models/lazy_person.rb @@ -25,4 +25,6 @@ class LazyPerson < ActiveRecord::Base decode: ->(raw) { raw && raw[3...-3] } vault_attribute :non_ascii + + vault_attribute :passport_number, convergent: true end diff --git a/spec/dummy/db/migrate/20181119142920_add_passport_number_to_people.rb b/spec/dummy/db/migrate/20181119142920_add_passport_number_to_people.rb new file mode 100644 index 00000000..a3993bd2 --- /dev/null +++ b/spec/dummy/db/migrate/20181119142920_add_passport_number_to_people.rb @@ -0,0 +1,5 @@ +class AddPassportNumberToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :passport_number_encrypted, :string + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 3b6e3c89..00efca22 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,30 +10,27 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2018_10_17_154000) do +ActiveRecord::Schema.define(version: 20181119142920) do create_table "people", force: :cascade do |t| - t.string "name" - t.string "ssn_encrypted" - t.string "cc_encrypted" - t.string "details_encrypted" - t.string "business_card_encrypted" - t.string "favorite_color_encrypted" - t.string "non_ascii_encrypted" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "email_encrypted" - t.string "address" - t.string "address_encrypted" - t.date "date_of_birth" - t.string "date_of_birth_encrypted" - t.string "integer_data_encrypted" - t.string "float_data_encrypted" - t.string "time_data_encrypted" - t.string "county" - t.string "county_encrypted" - t.string "state" - t.string "state_encrypted" + t.string "name" + t.string "ssn_encrypted" + t.string "cc_encrypted" + t.string "details_encrypted" + t.string "business_card_encrypted" + t.string "favorite_color_encrypted" + t.string "non_ascii_encrypted" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "email_encrypted" + t.string "integer_data_encrypted" + t.string "float_data_encrypted" + t.string "time_data_encrypted" + t.string "county" + t.string "county_encrypted" + t.string "state" + t.string "state_encrypted" + t.string "passport_number_encrypted" end end From 24722f1f23c5963864c2d52cc2c187de8df29ddc Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Mon, 19 Nov 2018 10:41:03 +0200 Subject: [PATCH 059/143] Add EncryptedModel methods for batch operations .vault_persist_all and .vault_load_all are methods, that can be used for batch operations of several records. .vault_load_all is useful only when in combination with lazy decryption. Note that although this commit is forwardported from the 0.6 version we've updated the implementation to work with the rails 5 attribute API we use in 0.7. --- lib/vault/encrypted_model.rb | 52 ++++++++++++++++++++++++++++++++++ spec/integration/rails_spec.rb | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index bba6f566..d8aa0f79 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -211,6 +211,58 @@ def vault_lazy_decrypt? def vault_lazy_decrypt! @vault_lazy_decrypt = true end + + # works only with convergent encryption + def vault_persist_all(attribute, records, plaintexts) + options = __vault_attributes[attribute] + + key = options[:key] + path = options[:path] + serializer = options[:serializer] + column = options[:encrypted_column] + + # Apply the serialize to the plaintext values, if one exists + if serializer + plaintexts = plaintexts.map { |plaintext| serializer.encode(plaintext) } + end + + # Generate the ciphertext and store it back as an attribute + ciphertexts = Vault::Rails.batch_encrypt(path, key, plaintexts, Vault.client) + + records.each_with_index do |record, index| + record.send("#{column}=", ciphertexts[index]) + record.save + end + end + + # works only with convergent encryption + # relevant only if lazy decryption is enabled + def vault_load_all(attribute, records) + options = __vault_attributes[attribute] + + key = options[:key] + path = options[:path] + serializer = options[:serializer] + column = options[:encrypted_column] + + # Load the ciphertext + ciphertexts = records.map { |record| record.read_attribute(column) } + + # Load the plaintext value + plaintexts = Vault::Rails.batch_decrypt(path, key, ciphertexts, Vault.client) + + # Deserialize the plaintext values, if a serializer exists + if serializer + plaintexts = plaintexts.map { |plaintext| serializer.decode(plaintext) } + end + + records.each_with_index do |record, index| + record.__vault_loaded_attributes << attribute + + # Write the virtual attribute with the plaintext value + record.write_attribute(attribute, plaintexts[index]) + end + end end included do diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index d3dfcdd9..fe6724b0 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -499,4 +499,56 @@ end end end + + context 'batch encryption and decryption' do + before do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + describe '.vault_load_all' do + it 'calls Vault just once' do + first_person = LazyPerson.create!(passport_number: '12345678') + second_person = LazyPerson.create!(passport_number: '12345679') + + people = [first_person.reload, second_person.reload] + expect(Vault.logical).to receive(:write).once.and_call_original + LazyPerson.vault_load_all(:passport_number, people) + + first_person.passport_number + second_person.passport_number + end + + it 'loads the attribute of all records' do + first_person = LazyPerson.create!(passport_number: '12345678') + second_person = LazyPerson.create!(passport_number: '12345679') + + first_person.reload + second_person.reload + + LazyPerson.vault_load_all(:passport_number, [first_person, second_person]) + expect(first_person.passport_number).to eq('12345678') + expect(second_person.passport_number).to eq('12345679') + end + end + + describe '.vault_persist_all' do + it 'calls Vault just once' do + first_person = LazyPerson.new + second_person = LazyPerson.new + + expect(Vault.logical).to receive(:write).once.and_call_original + LazyPerson.vault_persist_all(:passport_number, [first_person, second_person], %w(12345678 12345679)) + end + + it 'saves the attribute of all records' do + first_person = LazyPerson.new + second_person = LazyPerson.new + + LazyPerson.vault_persist_all(:passport_number, [first_person, second_person], %w(12345678 12345679)) + + expect(first_person.reload.passport_number).to eq('12345678') + expect(second_person.reload.passport_number).to eq('12345679') + end + end + end end From 222f0e7963a41b855f110018e5a0f333b33cf0c1 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Tue, 20 Nov 2018 16:19:20 +0200 Subject: [PATCH 060/143] Extract attribute batch operations in PerformInBatches For both encryption and decryption we need the same initialization phase of reading the configuration options. The idea is to avoid code repetition and cluttering even more code in EncryptedModel. Note that although this commit is forwardported from the 0.6 version we've updated the implementation to work with the rails 5 attribute API we use in 0.7. --- lib/vault/encrypted_model.rb | 41 +----- lib/vault/perform_in_batches.rb | 57 +++++++++ lib/vault/rails.rb | 1 + spec/unit/perform_in_batches_spec.rb | 184 +++++++++++++++++++++++++++ 4 files changed, 244 insertions(+), 39 deletions(-) create mode 100644 lib/vault/perform_in_batches.rb create mode 100644 spec/unit/perform_in_batches_spec.rb diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index d8aa0f79..1c25aacc 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -216,23 +216,7 @@ def vault_lazy_decrypt! def vault_persist_all(attribute, records, plaintexts) options = __vault_attributes[attribute] - key = options[:key] - path = options[:path] - serializer = options[:serializer] - column = options[:encrypted_column] - - # Apply the serialize to the plaintext values, if one exists - if serializer - plaintexts = plaintexts.map { |plaintext| serializer.encode(plaintext) } - end - - # Generate the ciphertext and store it back as an attribute - ciphertexts = Vault::Rails.batch_encrypt(path, key, plaintexts, Vault.client) - - records.each_with_index do |record, index| - record.send("#{column}=", ciphertexts[index]) - record.save - end + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) end # works only with convergent encryption @@ -240,28 +224,7 @@ def vault_persist_all(attribute, records, plaintexts) def vault_load_all(attribute, records) options = __vault_attributes[attribute] - key = options[:key] - path = options[:path] - serializer = options[:serializer] - column = options[:encrypted_column] - - # Load the ciphertext - ciphertexts = records.map { |record| record.read_attribute(column) } - - # Load the plaintext value - plaintexts = Vault::Rails.batch_decrypt(path, key, ciphertexts, Vault.client) - - # Deserialize the plaintext values, if a serializer exists - if serializer - plaintexts = plaintexts.map { |plaintext| serializer.decode(plaintext) } - end - - records.each_with_index do |record, index| - record.__vault_loaded_attributes << attribute - - # Write the virtual attribute with the plaintext value - record.write_attribute(attribute, plaintexts[index]) - end + Vault::PerformInBatches.new(attribute, options).decrypt(records) end end diff --git a/lib/vault/perform_in_batches.rb b/lib/vault/perform_in_batches.rb new file mode 100644 index 00000000..bf690d7e --- /dev/null +++ b/lib/vault/perform_in_batches.rb @@ -0,0 +1,57 @@ +module Vault + class PerformInBatches + def initialize(attribute, options) + @attribute = attribute + + @key = options[:key] + @path = options[:path] + @serializer = options[:serializer] + @column = options[:encrypted_column] + @convergent = options[:convergent] + end + + def encrypt(records, plaintexts) + raise 'Batch Operations work only with convergent attributes' unless @convergent + + raw_plaintexts = serialize(plaintexts) + + ciphertexts = Vault::Rails.batch_encrypt(path, key, raw_plaintexts, Vault.client) + + records.each_with_index do |record, index| + record.send("#{column}=", ciphertexts[index]) + record.save + end + end + + def decrypt(records) + raise 'Batch Operations work only with convergent attributes' unless @convergent + + ciphertexts = records.map { |record| record.send(column) } + + raw_plaintexts = Vault::Rails.batch_decrypt(path, key, ciphertexts, Vault.client) + plaintexts = deserialize(raw_plaintexts) + + records.each_with_index do |record, index| + record.__vault_loaded_attributes << attribute + + record.write_attribute(attribute, plaintexts[index]) + end + end + + private + + attr_reader :key, :path, :serializer, :column, :attribute + + def serialize(plaintexts) + return plaintexts unless serializer + + plaintexts.map { |plaintext| serializer.encode(plaintext) } + end + + def deserialize(plaintexts) + return plaintexts unless serializer + + plaintexts.map { |plaintext| serializer.decode(plaintext) } + end + end +end diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 363069ac..3956c91d 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -5,6 +5,7 @@ require_relative 'encrypted_model' require_relative 'attribute_proxy' +require_relative 'perform_in_batches' require_relative 'rails/configurable' require_relative 'rails/errors' require_relative 'rails/serializers/json_serializer' diff --git a/spec/unit/perform_in_batches_spec.rb b/spec/unit/perform_in_batches_spec.rb new file mode 100644 index 00000000..bafdc93e --- /dev/null +++ b/spec/unit/perform_in_batches_spec.rb @@ -0,0 +1,184 @@ +require 'spec_helper' + +describe Vault::PerformInBatches do + describe '#encrypt' do + context 'non-convergent attribute' do + let(:options) do + { + key: 'test_key', + path: 'test_path', + column: 'test_attribute_encrypted', + convergent: false + } + end + + it 'raises an exception for non-convergent attributes' do + attribute = 'test_attribute' + records = [double(:first_object, save: true), double(:second_object, save: true)] + plaintexts = %w(plaintext1 plaintext2) + + expect do + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) + end.to raise_error 'Batch Operations work only with convergent attributes' + end + end + + context 'convergent attribute' do + let(:options) do + { + key: 'test_key', + path: 'test_path', + encrypted_column: 'test_attribute_encrypted', + convergent: true + } + end + + it 'encrypts one attribute for a batch of records and saves it' do + attribute = 'test_attribute' + + first_record = double(save: true) + second_record = double(save: true) + records = [first_record, second_record] + + plaintexts = %w(plaintext1 plaintext2) + + + expect(Vault::Rails).to receive(:batch_encrypt) + .with('test_path', 'test_key', %w(plaintext1 plaintext2), Vault.client) + .and_return(%w(ciphertext1 ciphertext2)) + + expect(first_record).to receive('test_attribute_encrypted=').with('ciphertext1') + expect(second_record).to receive('test_attribute_encrypted=').with('ciphertext2') + + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) + end + + context 'with given serializer' do + let(:options) do + { + key: 'test_key', + path: 'test_path', + encrypted_column: 'test_attribute_encrypted', + serializer: Vault::Rails::Serializers::IntegerSerializer, + convergent: true + } + end + + it 'encrypts one attribute for a batch of records and saves it' do + attribute = 'test_attribute' + + first_record = double(save: true) + second_record = double(save: true) + records = [first_record, second_record] + + plaintexts = [100, 200] + + expect(Vault::Rails).to receive(:batch_encrypt) + .with('test_path', 'test_key', %w(100 200), Vault.client) + .and_return(%w(ciphertext1 ciphertext2)) + + expect(first_record).to receive('test_attribute_encrypted=').with('ciphertext1') + expect(second_record).to receive('test_attribute_encrypted=').with('ciphertext2') + + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) + end + end + end + end + + describe '#decrypt' do + context 'non-convergent attribute' do + let(:options) do + { + key: 'test_key', + path: 'test_path', + column: 'test_attribute_encrypted', + convergent: false + } + end + + it 'raises an exception for non-convergent attributes' do + attribute = 'test_attribute' + records = [double(:first_object, save: true), double(:second_object, save: true)] + plaintexts = %w(plaintext1 plaintext2) + + expect do + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) + end.to raise_error 'Batch Operations work only with convergent attributes' + end + end + + context 'convergent attribute' do + let(:options) do + { + key: 'test_key', + path: 'test_path', + encrypted_column: 'test_attribute_encrypted', + convergent: true + } + end + + it 'decrypts one attribute for a batch of records and loads it' do + attribute = 'test_attribute' + + first_record = double(test_attribute_encrypted: 'ciphertext1') + second_record = double(test_attribute_encrypted: 'ciphertext2') + records = [first_record, second_record] + + expect(Vault::Rails).to receive(:batch_decrypt) + .with('test_path', 'test_key', %w(ciphertext1 ciphertext2), Vault.client) + .and_return(%w(plaintext1 plaintext2)) + + first_record_loaded_attributes = [] + allow(first_record).to receive('__vault_loaded_attributes').and_return(first_record_loaded_attributes) + second_record_loaded_attributes = [] + allow(second_record).to receive('__vault_loaded_attributes').and_return(second_record_loaded_attributes) + + expect(first_record).to receive('write_attribute').with('test_attribute', 'plaintext1') + expect(second_record).to receive('write_attribute').with('test_attribute', 'plaintext2') + + Vault::PerformInBatches.new(attribute, options).decrypt(records) + + expect(first_record_loaded_attributes).to include(attribute) + expect(second_record_loaded_attributes).to include(attribute) + end + + context 'with given serializer' do + let(:options) do + { + key: 'test_key', + path: 'test_path', + encrypted_column: 'test_attribute_encrypted', + serializer: Vault::Rails::Serializers::IntegerSerializer, + convergent: true + } + end + + it 'decrypts one attribute for a batch of records and loads it' do + attribute = 'test_attribute' + + first_record = double(test_attribute_encrypted: 'ciphertext1') + second_record = double(test_attribute_encrypted: 'ciphertext2') + records = [first_record, second_record] + + expect(Vault::Rails).to receive(:batch_decrypt) + .with('test_path', 'test_key', %w(ciphertext1 ciphertext2), Vault.client) + .and_return(%w(100 200)) + + first_record_loaded_attributes = [] + allow(first_record).to receive('__vault_loaded_attributes').and_return(first_record_loaded_attributes) + second_record_loaded_attributes = [] + allow(second_record).to receive('__vault_loaded_attributes').and_return(second_record_loaded_attributes) + + expect(first_record).to receive('write_attribute').with('test_attribute', 100) + expect(second_record).to receive('write_attribute').with('test_attribute', 200) + + Vault::PerformInBatches.new(attribute, options).decrypt(records) + + expect(first_record_loaded_attributes).to include(attribute) + expect(second_record_loaded_attributes).to include(attribute) + end + end + end + end +end From b67bc0d159c70e243d847e0363ac7ca20ad165ce Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Wed, 12 Dec 2018 12:44:26 +0000 Subject: [PATCH 061/143] Add changelog entries for the features we ported from 0.6 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 837d75c3..a431f6f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Vault Rails Changelog +## Unreleased + +IMPROVEMENTS +- Add `EncryptedModel.vault_persist_all` for encrypting and saving one attribute of multiple records with just one call to Vault (forward ported from 0.6.5) +- Add `EncryptedModel.vault_load_all` for decrypting and loading one attribute of multiple records with just one call to Vault (forward ported from 0.6.5) + ## 0.7.3 (December 10, 2018) BUG FIXES From bd5d47ffe5e7e31184bab783240837b523405edd Mon Sep 17 00:00:00 2001 From: Murray Steele Date: Wed, 12 Dec 2018 15:10:33 +0000 Subject: [PATCH 062/143] Create v0.7.4 --- CHANGELOG.md | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a431f6f0..b5d6e8ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Vault Rails Changelog -## Unreleased +## 0.7.4 (December 12, 2018) IMPROVEMENTS - Add `EncryptedModel.vault_persist_all` for encrypting and saving one attribute of multiple records with just one call to Vault (forward ported from 0.6.5) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 8d44102b..c8eb6fcd 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.3) + fc-vault-rails (0.7.4) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index a9681caf..9e9ed1fb 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.3) + fc-vault-rails (0.7.4) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index c0922d0b..0f558b92 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.3) + fc-vault-rails (0.7.4) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 40fad252..50a9c00b 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.3" + VERSION = "0.7.4" end end From f284c24402c618378282749797a7c197e477737d Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 12 Dec 2018 14:07:37 +0200 Subject: [PATCH 063/143] Add option for uniqueness validation of vault attributes Only convergent attributes can be checked for uniqueness --- README.md | 9 +++ lib/vault/rails.rb | 1 + lib/vault/rails/vault_uniqueness_validator.rb | 32 +++++++++ spec/dummy/app/models/person.rb | 6 ++ ...licence_number_and_ip_address_to_people.rb | 6 ++ spec/dummy/db/schema.rb | 8 ++- spec/integration/rails_spec.rb | 68 +++++++++++++++++++ 7 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 lib/vault/rails/vault_uniqueness_validator.rb create mode 100644 spec/dummy/db/migrate/20181212095513_add_driving_licence_number_and_ip_address_to_people.rb diff --git a/README.md b/README.md index d9aee2ee..7e0a76c1 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,15 @@ Person.where(ssn: "123-45-6789") This is because the database is unaware of the plain-text data (which is part of the security model). +### Uniqueness Validation +If a column is **convergently** encrypted, it is possible to add a validation of uniqueness to it. +Example: +```ruby +validates :driving_licence_number, vault_uniqueness: true +``` + +It is highly advisable that you also add a uniqueness constraint at database level. + ### Vault Attribute Proxy This method is useful if you have a plaintext attribute that you want to replace with a vault attribute. During a transition period both attributes can be seamlessly read/changed at the same time. diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 3956c91d..07406c18 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -8,6 +8,7 @@ require_relative 'perform_in_batches' require_relative 'rails/configurable' require_relative 'rails/errors' +require_relative 'rails/vault_uniqueness_validator' require_relative 'rails/serializers/json_serializer' require_relative 'rails/serializers/date_serializer' require_relative 'rails/serializers/integer_serializer' diff --git a/lib/vault/rails/vault_uniqueness_validator.rb b/lib/vault/rails/vault_uniqueness_validator.rb new file mode 100644 index 00000000..eafd9f97 --- /dev/null +++ b/lib/vault/rails/vault_uniqueness_validator.rb @@ -0,0 +1,32 @@ +require 'active_record' + +class VaultUniquenessValidator < ActiveRecord::Validations::UniquenessValidator + def validate_each(record, attribute, value) + attribute_options = vault_options(record, attribute) + + unless attribute_options[:convergent] + raise 'You cannot check uniqueness of an attribute that is not convergently encrypted' + end + + encrypted_column = attribute_options[:encrypted_column] + + encrypted_value = value.present? ? encrypt_value(value, attribute_options) : value + + super(record, encrypted_column, encrypted_value) + end + + private + + def vault_options(record, attribute) + record.class.__vault_attributes[attribute] + end + + def encrypt_value(value, attribute_options) + key = attribute_options[:key] + serializer = attribute_options[:serializer] + + plaintext = serializer ? serializer.encode(value) : value + + Vault::Rails.encrypt('transit', key, plaintext, Vault.client, true) + end +end diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index 1324df9b..25461dbb 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -30,6 +30,12 @@ class Person < ActiveRecord::Base vault_attribute :email, convergent: true + vault_attribute :driving_licence_number, convergent: true + validates :driving_licence_number, vault_uniqueness: true, allow_nil: true + + vault_attribute :ip_address, convergent: true, serialize: :ipaddr + validates :ip_address, vault_uniqueness: true, allow_nil: true + vault_attribute :integer_data, type: :integer, serialize: :integer diff --git a/spec/dummy/db/migrate/20181212095513_add_driving_licence_number_and_ip_address_to_people.rb b/spec/dummy/db/migrate/20181212095513_add_driving_licence_number_and_ip_address_to_people.rb new file mode 100644 index 00000000..bb5a7456 --- /dev/null +++ b/spec/dummy/db/migrate/20181212095513_add_driving_licence_number_and_ip_address_to_people.rb @@ -0,0 +1,6 @@ +class AddDrivingLicenceNumberAndIpAddressToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :driving_licence_number_encrypted, :string + add_column :people, :ip_address_encrypted, :string + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index 00efca22..f9e531a7 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181119142920) do +ActiveRecord::Schema.define(version: 20181212095513) do create_table "people", force: :cascade do |t| t.string "name" @@ -20,8 +20,8 @@ t.string "business_card_encrypted" t.string "favorite_color_encrypted" t.string "non_ascii_encrypted" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "email_encrypted" t.string "integer_data_encrypted" t.string "float_data_encrypted" @@ -31,6 +31,8 @@ t.string "state" t.string "state_encrypted" t.string "passport_number_encrypted" + t.string "driving_licence_number_encrypted" + t.string "ip_address_encrypted" end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index fe6724b0..b33b009c 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -3,6 +3,10 @@ require "spec_helper" describe Vault::Rails do + before(:each) do + Person.delete_all + end + context "with default options" do before(:all) do Vault::Rails.logical.write("transit/keys/dummy_people_ssn") @@ -500,6 +504,70 @@ end end + context 'uniqueness validation' do + before do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + context 'new record with duplicated driving licence number' do + it 'is invalid' do + Person.create!(driving_licence_number: '12345678') + same_driving_licence_number_person = Person.new(driving_licence_number: '12345678') + + expect(same_driving_licence_number_person).not_to be_valid + end + end + + context 'new record with new different licence number' do + it 'is valid' do + Person.create!(driving_licence_number: '12345678') + different_driving_licence_number_person = Person.new(driving_licence_number: '12345679') + + expect(different_driving_licence_number_person).to be_valid + end + end + + context 'old record with duplicated driving licence number' do + it 'is invalid' do + Person.create!(driving_licence_number: '12345678') + another_person = Person.create!(driving_licence_number: '12345679') + another_person.driving_licence_number = '12345678' + + expect(another_person).not_to be_valid + end + end + + context 'attribute with defined serializer' do + context 'new record with duplicated IP address' do + it 'is invalid' do + person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) + same_ip_address_person = Person.new(ip_address: IPAddr.new('127.0.0.1')) + + expect(same_ip_address_person).not_to be_valid + end + end + + context 'new record with different IP address' do + it 'is valid' do + Person.create!(ip_address: IPAddr.new('127.0.0.1')) + different_ip_address_person = Person.new(ip_address: IPAddr.new('192.168.0.1')) + + expect(different_ip_address_person).to be_valid + end + end + + context 'old record with duplicated IP address' do + it 'is invalid' do + Person.create!(ip_address: IPAddr.new('127.0.0.1')) + another_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + another_person.ip_address = IPAddr.new('127.0.0.1') + + expect(another_person).not_to be_valid + end + end + end + end + context 'batch encryption and decryption' do before do allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) From eefca32b71a7850d69f2d8ff8c376353750b38e3 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Fri, 14 Dec 2018 09:11:23 +0200 Subject: [PATCH 064/143] Encrypt plaintext in a separate method `.encrypt_value` Encrypting values is necessary not only in attribute setters, but also in the uniqueness validator. In order to avoid duplication, the encryption is extracted in a separate method --- lib/vault/encrypted_model.rb | 27 +++++++++++-------- lib/vault/rails/vault_uniqueness_validator.rb | 11 +------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 1c25aacc..5007c959 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -226,6 +226,20 @@ def vault_load_all(attribute, records) Vault::PerformInBatches.new(attribute, options).decrypt(records) end + + def encrypt_value(attribute, value) + options = __vault_attributes[attribute] + + key = options[:key] + path = options[:path] + serializer = options[:serializer] + convergent = options[:convergent] + + # Apply the serializer to the value, if one exists + plaintext = serializer ? serializer.encode(value) : value + + Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) + end end included do @@ -326,22 +340,13 @@ def __vault_encrypt_attribute!(attribute, options, in_after_save: false) return unless changed.include?("#{attribute}") end - key = options[:key] - path = options[:path] - serializer = options[:serializer] - column = options[:encrypted_column] - convergent = options[:convergent] + column = options[:encrypted_column] # Get the current value of the plaintext attribute plaintext = read_attribute(attribute) - # Apply the serialize to the plaintext value, if one exists - if serializer - plaintext = serializer.encode(plaintext) - end - # Generate the ciphertext and store it back as an attribute - ciphertext = Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) + ciphertext = self.class.encrypt_value(attribute, plaintext) # Write the attribute back, so that we don't have to reload the record # to get the ciphertext diff --git a/lib/vault/rails/vault_uniqueness_validator.rb b/lib/vault/rails/vault_uniqueness_validator.rb index eafd9f97..0f05f55f 100644 --- a/lib/vault/rails/vault_uniqueness_validator.rb +++ b/lib/vault/rails/vault_uniqueness_validator.rb @@ -10,7 +10,7 @@ def validate_each(record, attribute, value) encrypted_column = attribute_options[:encrypted_column] - encrypted_value = value.present? ? encrypt_value(value, attribute_options) : value + encrypted_value = record.class.encrypt_value(attribute, value) super(record, encrypted_column, encrypted_value) end @@ -20,13 +20,4 @@ def validate_each(record, attribute, value) def vault_options(record, attribute) record.class.__vault_attributes[attribute] end - - def encrypt_value(value, attribute_options) - key = attribute_options[:key] - serializer = attribute_options[:serializer] - - plaintext = serializer ? serializer.encode(value) : value - - Vault::Rails.encrypt('transit', key, plaintext, Vault.client, true) - end end From 25515a6f5eb03ddff8e2e0a547c85e9a7066c135 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Thu, 13 Dec 2018 17:10:02 +0200 Subject: [PATCH 065/143] Add ability to search by vault attributes You can search only by convergently encrypted attributes --- lib/vault/encrypted_model.rb | 19 ++++++++++++++++- spec/integration/rails_spec.rb | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 5007c959..95efca23 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -91,7 +91,7 @@ def vault_attribute(attribute_name, options = {}) self end - # Encrypt Vault attribures before saving them + # Encrypt Vault attributes before saving them def vault_persist_before_save! skip_callback :save, :after, :__vault_persist_attributes! before_save :__vault_encrypt_attributes! @@ -240,6 +240,23 @@ def encrypt_value(attribute, value) Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) end + + def find_by_vault_attributes(attributes) + search_options = {} + + attributes.each do |attribute_name, attribute_value| + attribute_options = __vault_attributes[attribute_name] + encrypted_column = attribute_options[:encrypted_column] + + unless attribute_options[:convergent] + raise ArgumentError, 'You cannot search with non-convergent fields' + end + + search_options[encrypted_column] = encrypt_value(attribute_name, attribute_value) + end + + where(search_options) + end end included do diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index b33b009c..2f366653 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -619,4 +619,41 @@ end end end + + describe '.find_by_vault_attributes' do + before do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + it 'finds the expected records' do + first_person = LazyPerson.create!(passport_number: '12345678') + second_person = LazyPerson.create!(passport_number: '12345678') + third_person = LazyPerson.create!(passport_number: '87654321') + + expect(LazyPerson.find_by_vault_attributes(passport_number: '12345678').pluck(:id)).to match_array([first_person, second_person].map(&:id)) + end + + context 'searching by attributes with defined serializer' do + it 'finds the expected records' do + first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) + second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + + expect(Person.find_by_vault_attributes(ip_address: IPAddr.new('127.0.0.1')).pluck(:id)).to match_array([first_person.id]) + end + end + + context 'searching by multiple attributes' do + it 'finds the expected records' do + first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678') + + expect(Person.find_by_vault_attributes(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678').pluck(:id)).to match_array([first_person.id]) + end + end + + context 'non-convergently encrypted attributes' do + it 'raises an exception' do + expect { LazyPerson.find_by_vault_attributes(ssn: '12345678') }.to raise_error('You cannot search with non-convergent fields') + end + end + end end From 627a73a481b93abe7725111d08dfc1ae9460da18 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Fri, 14 Dec 2018 13:52:15 +0200 Subject: [PATCH 066/143] Update README about searching encrypted attributes --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7e0a76c1..4f33127e 100644 --- a/README.md +++ b/README.md @@ -274,15 +274,23 @@ Vault::Rails.batch_encrypt(path, key, , client) ### Searching Encrypted Attributes Because each column is uniquely encrypted, it is not possible to search for a -particular plain-text value. For example, if the `ssn` attribute is encrypted, +particular plain-text value with a plain `ActiveRecord` query. For example, if the `ssn` attribute is encrypted, the following will **NOT** work: ```ruby Person.where(ssn: "123-45-6789") ``` -This is because the database is unaware of the plain-text data (which is part of -the security model). +That's why we have added a method that provides an easy to use search interface. Instead of using `.where` you can use +`.find_by_vault_attributes`. Example: + +```ruby +Person.find_by_vault_attributes(driving_licence_number: '12345678') +``` + +This method will look up seamlessly in the relevant column with encrypted data. +It is important to note that you can search only for attributes with **convergent** encryption. +Similar to `.where` the method `.find_by_vault_attributes` also returns an `ActiveRecord::Relation` ### Uniqueness Validation If a column is **convergently** encrypted, it is possible to add a validation of uniqueness to it. From e30a0fd9f759844763eca789d432fa5e755bd4f3 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Mon, 17 Dec 2018 16:22:31 +0200 Subject: [PATCH 067/143] Bump version to v0.7.5 --- CHANGELOG.md | 6 ++++++ gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d6e8ae..8801d3b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Vault Rails Changelog +## 0.7.5 (December 17, 2018) + +IMPROVEMENTS +- Add method for database searching by convergently encrypted attributes +- Add uniqueness validator for convergently encrypted attributes + ## 0.7.4 (December 12, 2018) IMPROVEMENTS diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index c8eb6fcd..9541c6e5 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.4) + fc-vault-rails (0.7.5) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 9e9ed1fb..9d3a303f 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.4) + fc-vault-rails (0.7.5) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 0f558b92..a3061287 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.4) + fc-vault-rails (0.7.5) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 50a9c00b..e649c3ce 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.4" + VERSION = "0.7.5" end end From 995af7777d0d4e804220fc72a2a2a77028c9a4b7 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 27 Feb 2019 11:40:15 +0200 Subject: [PATCH 068/143] Add validate option to vault_persist_all In case of invalid records we might still want to persist them --- lib/vault/encrypted_model.rb | 4 ++-- lib/vault/perform_in_batches.rb | 4 ++-- spec/integration/rails_spec.rb | 11 +++++++++++ spec/unit/perform_in_batches_spec.rb | 25 +++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 95efca23..7cd3f247 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -213,10 +213,10 @@ def vault_lazy_decrypt! end # works only with convergent encryption - def vault_persist_all(attribute, records, plaintexts) + def vault_persist_all(attribute, records, plaintexts, validate: true) options = __vault_attributes[attribute] - Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts, validate: validate) end # works only with convergent encryption diff --git a/lib/vault/perform_in_batches.rb b/lib/vault/perform_in_batches.rb index bf690d7e..4244e4ec 100644 --- a/lib/vault/perform_in_batches.rb +++ b/lib/vault/perform_in_batches.rb @@ -10,7 +10,7 @@ def initialize(attribute, options) @convergent = options[:convergent] end - def encrypt(records, plaintexts) + def encrypt(records, plaintexts, validate: true) raise 'Batch Operations work only with convergent attributes' unless @convergent raw_plaintexts = serialize(plaintexts) @@ -19,7 +19,7 @@ def encrypt(records, plaintexts) records.each_with_index do |record, index| record.send("#{column}=", ciphertexts[index]) - record.save + record.save(validate: validate) end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 2f366653..088a76c9 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -617,6 +617,17 @@ expect(first_person.reload.passport_number).to eq('12345678') expect(second_person.reload.passport_number).to eq('12345679') end + + context 'skipped validations' do + it 'saves even invalid records' do + first_person = LazyPerson.new + allow(first_person).to receive(:valid?).and_return(false) + + LazyPerson.vault_persist_all(:passport_number, [first_person], %w(12345678), validate: false) + + expect(first_person.reload.passport_number).to eq('12345678') + end + end end end diff --git a/spec/unit/perform_in_batches_spec.rb b/spec/unit/perform_in_batches_spec.rb index bafdc93e..bbdd0b56 100644 --- a/spec/unit/perform_in_batches_spec.rb +++ b/spec/unit/perform_in_batches_spec.rb @@ -53,6 +53,31 @@ Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts) end + context 'with validation turned off' do + it 'encrypts one attribute for a batch of records and saves it without validations' do + attribute = 'test_attribute' + + first_record = double(save: true) + second_record = double(save: true) + records = [first_record, second_record] + + plaintexts = %w(plaintext1 plaintext2) + + + expect(Vault::Rails).to receive(:batch_encrypt) + .with('test_path', 'test_key', %w(plaintext1 plaintext2), Vault.client) + .and_return(%w(ciphertext1 ciphertext2)) + + expect(first_record).to receive('test_attribute_encrypted=').with('ciphertext1') + expect(second_record).to receive('test_attribute_encrypted=').with('ciphertext2') + + expect(first_record).to receive(:save).with(validate: false) + expect(second_record).to receive(:save).with(validate: false) + + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts, validate: false) + end + end + context 'with given serializer' do let(:options) do { From a519f24fccfdfd2fb615f3163614112d12d79541 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 27 Feb 2019 12:11:07 +0200 Subject: [PATCH 069/143] Update README about Vault batch operations --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 4f33127e..5dd3c228 100644 --- a/README.md +++ b/README.md @@ -271,6 +271,18 @@ Vault::Rails.batch_decrypt(path, key, , client) Vault::Rails.batch_encrypt(path, key, , client) ``` +Even easier, you could use: + +* ```EncryptedModel.vault_persist_all(attribute, records, plaintexts, validate: true)``` + + Encrypt all plaintext values and save them as the given attribute for the corresponding record + If you pass `validate: false` to `vault_persist_all` objects will be saved without validations. By default, validations are turned on. + +* ```EncryptedModel.vault_load_all(attribute, records)``` + + Decrypt and load the given attribute for each of the records + + ### Searching Encrypted Attributes Because each column is uniquely encrypted, it is not possible to search for a From 5285feac09df380aa37e1a2a9010428fcf49cbdb Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 27 Feb 2019 14:02:13 +0200 Subject: [PATCH 070/143] Drop support of ruby 2.2 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d78e5b7..916c07f3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,7 +21,6 @@ branches: - master rvm: - - 2.2.10 - 2.3.7 - 2.4.4 - 2.5.1 From 16c7210202df4b265623d6d4018f8e8a761d3bf4 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 27 Feb 2019 14:32:45 +0200 Subject: [PATCH 071/143] Set sqlite3 version to 1.13.6 and bundle update --- fc-vault-rails.gemspec | 2 +- gemfiles/rails_5.0.gemfile.lock | 82 ++++++++++++++-------------- gemfiles/rails_5.1.gemfile.lock | 84 ++++++++++++++--------------- gemfiles/rails_5.2.gemfile.lock | 94 ++++++++++++++++----------------- 4 files changed, 131 insertions(+), 131 deletions(-) diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 2bc6aeb9..2dac1af3 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -27,6 +27,6 @@ Gem::Specification.new do |s| s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec", "~> 3.2" - s.add_development_dependency "sqlite3" + s.add_development_dependency "sqlite3", "~> 1.3.6" s.add_development_dependency "wwtd" end diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 9541c6e5..834e28d7 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -8,39 +8,39 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (5.0.7) - actionpack (= 5.0.7) + actioncable (5.0.7.1) + actionpack (= 5.0.7.1) nio4r (>= 1.2, < 3.0) websocket-driver (~> 0.6.1) - actionmailer (5.0.7) - actionpack (= 5.0.7) - actionview (= 5.0.7) - activejob (= 5.0.7) + actionmailer (5.0.7.1) + actionpack (= 5.0.7.1) + actionview (= 5.0.7.1) + activejob (= 5.0.7.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.0.7) - actionview (= 5.0.7) - activesupport (= 5.0.7) + actionpack (5.0.7.1) + actionview (= 5.0.7.1) + activesupport (= 5.0.7.1) rack (~> 2.0) rack-test (~> 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.7) - activesupport (= 5.0.7) + actionview (5.0.7.1) + activesupport (= 5.0.7.1) builder (~> 3.1) erubis (~> 2.7.0) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.7) - activesupport (= 5.0.7) + activejob (5.0.7.1) + activesupport (= 5.0.7.1) globalid (>= 0.3.6) - activemodel (5.0.7) - activesupport (= 5.0.7) - activerecord (5.0.7) - activemodel (= 5.0.7) - activesupport (= 5.0.7) + activemodel (5.0.7.1) + activesupport (= 5.0.7.1) + activerecord (5.0.7.1) + activemodel (= 5.0.7.1) + activesupport (= 5.0.7.1) arel (~> 7.0) - activesupport (5.0.7) + activesupport (5.0.7.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -52,15 +52,15 @@ GEM arel (7.1.4) aws-sigv4 (1.0.3) builder (3.2.3) - byebug (10.0.2) + byebug (11.0.0) coderay (1.1.2) - concurrent-ruby (1.1.3) + concurrent-ruby (1.1.4) crass (1.0.4) diff-lcs (1.3) erubis (2.7.0) - globalid (0.4.1) + globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.1.1) + i18n (1.5.3) concurrent-ruby (~> 1.0) loofah (2.2.3) crass (~> 1.0.2) @@ -69,37 +69,37 @@ GEM mini_mime (>= 0.1.1) method_source (0.9.2) mini_mime (1.0.1) - mini_portile2 (2.3.0) + mini_portile2 (2.4.0) minitest (5.11.3) nio4r (2.3.1) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - pry (0.12.1) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) rack (2.0.6) rack-test (0.6.3) rack (>= 1.0) - rails (5.0.7) - actioncable (= 5.0.7) - actionmailer (= 5.0.7) - actionpack (= 5.0.7) - actionview (= 5.0.7) - activejob (= 5.0.7) - activemodel (= 5.0.7) - activerecord (= 5.0.7) - activesupport (= 5.0.7) + rails (5.0.7.1) + actioncable (= 5.0.7.1) + actionmailer (= 5.0.7.1) + actionpack (= 5.0.7.1) + actionview (= 5.0.7.1) + activejob (= 5.0.7.1) + activemodel (= 5.0.7.1) + activerecord (= 5.0.7.1) + activesupport (= 5.0.7.1) bundler (>= 1.3.0) - railties (= 5.0.7) + railties (= 5.0.7.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.0.4) loofah (~> 2.2, >= 2.2.2) - railties (5.0.7) - actionpack (= 5.0.7) - activesupport (= 5.0.7) + railties (5.0.7.1) + actionpack (= 5.0.7.1) + activesupport (= 5.0.7.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -148,7 +148,7 @@ DEPENDENCIES rails (~> 5.0.0) rake (~> 10.0) rspec (~> 3.2) - sqlite3 + sqlite3 (~> 1.3.6) wwtd BUNDLED WITH diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 9d3a303f..0ad50334 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -8,39 +8,39 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (5.1.6) - actionpack (= 5.1.6) + actioncable (5.1.6.1) + actionpack (= 5.1.6.1) nio4r (~> 2.0) websocket-driver (~> 0.6.1) - actionmailer (5.1.6) - actionpack (= 5.1.6) - actionview (= 5.1.6) - activejob (= 5.1.6) + actionmailer (5.1.6.1) + actionpack (= 5.1.6.1) + actionview (= 5.1.6.1) + activejob (= 5.1.6.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.1.6) - actionview (= 5.1.6) - activesupport (= 5.1.6) + actionpack (5.1.6.1) + actionview (= 5.1.6.1) + activesupport (= 5.1.6.1) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.6) - activesupport (= 5.1.6) + actionview (5.1.6.1) + activesupport (= 5.1.6.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.1.6) - activesupport (= 5.1.6) + activejob (5.1.6.1) + activesupport (= 5.1.6.1) globalid (>= 0.3.6) - activemodel (5.1.6) - activesupport (= 5.1.6) - activerecord (5.1.6) - activemodel (= 5.1.6) - activesupport (= 5.1.6) + activemodel (5.1.6.1) + activesupport (= 5.1.6.1) + activerecord (5.1.6.1) + activemodel (= 5.1.6.1) + activesupport (= 5.1.6.1) arel (~> 8.0) - activesupport (5.1.6) + activesupport (5.1.6.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -52,15 +52,15 @@ GEM arel (8.0.0) aws-sigv4 (1.0.3) builder (3.2.3) - byebug (10.0.2) + byebug (11.0.0) coderay (1.1.2) - concurrent-ruby (1.1.3) + concurrent-ruby (1.1.4) crass (1.0.4) diff-lcs (1.3) - erubi (1.7.1) - globalid (0.4.1) + erubi (1.8.0) + globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.1.1) + i18n (1.5.3) concurrent-ruby (~> 1.0) loofah (2.2.3) crass (~> 1.0.2) @@ -69,37 +69,37 @@ GEM mini_mime (>= 0.1.1) method_source (0.9.2) mini_mime (1.0.1) - mini_portile2 (2.3.0) + mini_portile2 (2.4.0) minitest (5.11.3) nio4r (2.3.1) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - pry (0.12.1) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) rack (2.0.6) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.1.6) - actioncable (= 5.1.6) - actionmailer (= 5.1.6) - actionpack (= 5.1.6) - actionview (= 5.1.6) - activejob (= 5.1.6) - activemodel (= 5.1.6) - activerecord (= 5.1.6) - activesupport (= 5.1.6) + rails (5.1.6.1) + actioncable (= 5.1.6.1) + actionmailer (= 5.1.6.1) + actionpack (= 5.1.6.1) + actionview (= 5.1.6.1) + activejob (= 5.1.6.1) + activemodel (= 5.1.6.1) + activerecord (= 5.1.6.1) + activesupport (= 5.1.6.1) bundler (>= 1.3.0) - railties (= 5.1.6) + railties (= 5.1.6.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.0.4) loofah (~> 2.2, >= 2.2.2) - railties (5.1.6) - actionpack (= 5.1.6) - activesupport (= 5.1.6) + railties (5.1.6.1) + actionpack (= 5.1.6.1) + activesupport (= 5.1.6.1) method_source rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) @@ -148,7 +148,7 @@ DEPENDENCIES rails (~> 5.1.0) rake (~> 10.0) rspec (~> 3.2) - sqlite3 + sqlite3 (~> 1.3.6) wwtd BUNDLED WITH diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index a3061287..4721be74 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -8,43 +8,43 @@ PATH GEM remote: https://rubygems.org/ specs: - actioncable (5.2.1) - actionpack (= 5.2.1) + actioncable (5.2.2) + actionpack (= 5.2.2) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailer (5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) + actionmailer (5.2.2) + actionpack (= 5.2.2) + actionview (= 5.2.2) + activejob (= 5.2.2) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.2.1) - actionview (= 5.2.1) - activesupport (= 5.2.1) + actionpack (5.2.2) + actionview (= 5.2.2) + activesupport (= 5.2.2) rack (~> 2.0) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.1) - activesupport (= 5.2.1) + actionview (5.2.2) + activesupport (= 5.2.2) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.1) - activesupport (= 5.2.1) + activejob (5.2.2) + activesupport (= 5.2.2) globalid (>= 0.3.6) - activemodel (5.2.1) - activesupport (= 5.2.1) - activerecord (5.2.1) - activemodel (= 5.2.1) - activesupport (= 5.2.1) + activemodel (5.2.2) + activesupport (= 5.2.2) + activerecord (5.2.2) + activemodel (= 5.2.2) + activesupport (= 5.2.2) arel (>= 9.0) - activestorage (5.2.1) - actionpack (= 5.2.1) - activerecord (= 5.2.1) + activestorage (5.2.2) + actionpack (= 5.2.2) + activerecord (= 5.2.2) marcel (~> 0.3.1) - activesupport (5.2.1) + activesupport (5.2.2) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 0.7, < 2) minitest (~> 5.1) @@ -56,15 +56,15 @@ GEM arel (9.0.0) aws-sigv4 (1.0.3) builder (3.2.3) - byebug (10.0.2) + byebug (11.0.0) coderay (1.1.2) - concurrent-ruby (1.1.3) + concurrent-ruby (1.1.4) crass (1.0.4) diff-lcs (1.3) - erubi (1.7.1) - globalid (0.4.1) + erubi (1.8.0) + globalid (0.4.2) activesupport (>= 4.2.0) - i18n (1.1.1) + i18n (1.5.3) concurrent-ruby (~> 1.0) loofah (2.2.3) crass (~> 1.0.2) @@ -74,40 +74,40 @@ GEM marcel (0.3.3) mimemagic (~> 0.3.2) method_source (0.9.2) - mimemagic (0.3.2) + mimemagic (0.3.3) mini_mime (1.0.1) - mini_portile2 (2.3.0) + mini_portile2 (2.4.0) minitest (5.11.3) nio4r (2.3.1) - nokogiri (1.8.5) - mini_portile2 (~> 2.3.0) - pry (0.12.1) + nokogiri (1.10.1) + mini_portile2 (~> 2.4.0) + pry (0.12.2) coderay (~> 1.1.0) method_source (~> 0.9.0) rack (2.0.6) rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.2.1) - actioncable (= 5.2.1) - actionmailer (= 5.2.1) - actionpack (= 5.2.1) - actionview (= 5.2.1) - activejob (= 5.2.1) - activemodel (= 5.2.1) - activerecord (= 5.2.1) - activestorage (= 5.2.1) - activesupport (= 5.2.1) + rails (5.2.2) + actioncable (= 5.2.2) + actionmailer (= 5.2.2) + actionpack (= 5.2.2) + actionview (= 5.2.2) + activejob (= 5.2.2) + activemodel (= 5.2.2) + activerecord (= 5.2.2) + activestorage (= 5.2.2) + activesupport (= 5.2.2) bundler (>= 1.3.0) - railties (= 5.2.1) + railties (= 5.2.2) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) rails-html-sanitizer (1.0.4) loofah (~> 2.2, >= 2.2.2) - railties (5.2.1) - actionpack (= 5.2.1) - activesupport (= 5.2.1) + railties (5.2.2) + actionpack (= 5.2.2) + activesupport (= 5.2.2) method_source rake (>= 0.8.7) thor (>= 0.19.0, < 2.0) @@ -156,7 +156,7 @@ DEPENDENCIES rails (~> 5.2.0) rake (~> 10.0) rspec (~> 3.2) - sqlite3 + sqlite3 (~> 1.3.6) wwtd BUNDLED WITH From 2d11249c01d1d927ac6656c725233ba5e63e2ef1 Mon Sep 17 00:00:00 2001 From: Martin Popov Date: Wed, 27 Feb 2019 12:11:19 +0200 Subject: [PATCH 072/143] Bump version to 0.7.6 --- CHANGELOG.md | 5 +++++ gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8801d3b7..42e8ddda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Vault Rails Changelog +## 0.7.6 (February 27, 2019) + +IMPROVEMENTS +- Add option to `PerformInBatches#encrypt` and `EncryptedModel.vault_persist_all` to skip `ActiveRecord` validations +- Drop support of Ruby 2.2 ## 0.7.5 (December 17, 2018) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 834e28d7..059c0b6d 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.5) + fc-vault-rails (0.7.6) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 0ad50334..eee3fd4d 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.5) + fc-vault-rails (0.7.6) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 4721be74..fa935830 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.5) + fc-vault-rails (0.7.6) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index e649c3ce..2cbfc7e8 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.5" + VERSION = "0.7.6" end end From f9ca096f3a85ad568de21797271840effcad5c09 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 6 Mar 2019 12:45:18 +0000 Subject: [PATCH 073/143] Cleanse error messages when uniqueness validation is used --- lib/vault/rails/vault_uniqueness_validator.rb | 9 +++++++++ spec/integration/rails_spec.rb | 2 ++ 2 files changed, 11 insertions(+) diff --git a/lib/vault/rails/vault_uniqueness_validator.rb b/lib/vault/rails/vault_uniqueness_validator.rb index 0f05f55f..828a5e32 100644 --- a/lib/vault/rails/vault_uniqueness_validator.rb +++ b/lib/vault/rails/vault_uniqueness_validator.rb @@ -13,6 +13,8 @@ def validate_each(record, attribute, value) encrypted_value = record.class.encrypt_value(attribute, value) super(record, encrypted_column, encrypted_value) + + cleanse_error_message(record, attribute, encrypted_column, encrypted_value) end private @@ -20,4 +22,11 @@ def validate_each(record, attribute, value) def vault_options(record, attribute) record.class.__vault_attributes[attribute] end + + def cleanse_error_message(record, attribute, encrypted_column, encrypted_value) + return unless record.errors.key?(encrypted_column.to_sym) + + record.errors.delete(encrypted_column.to_sym) + record.errors.add(attribute, :taken, value: encrypted_value) + end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 088a76c9..3ac364ab 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -515,6 +515,8 @@ same_driving_licence_number_person = Person.new(driving_licence_number: '12345678') expect(same_driving_licence_number_person).not_to be_valid + expect(same_driving_licence_number_person.errors[:driving_licence_number]).to include('has already been taken') + expect(same_driving_licence_number_person.errors[:driving_licence_number_encrypted]).not_to include('has already been taken') end end From 24cc468dc3633b7500ec4d655b129036390dfd6e Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Wed, 6 Mar 2019 16:41:50 +0000 Subject: [PATCH 074/143] Bump version to 0.7.7 --- CHANGELOG.md | 5 +++++ gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42e8ddda..3639a6f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Vault Rails Changelog +## 0.7.7 (March 6, 2019) + +IMPROVEMENTS +- Updates error message when `vault_uniqueness` is used, so now the `vault_attribute`'s name is used rather than the encrypted column name + ## 0.7.6 (February 27, 2019) IMPROVEMENTS diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 059c0b6d..8e52a450 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.6) + fc-vault-rails (0.7.7) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index eee3fd4d..8e5d9dae 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.6) + fc-vault-rails (0.7.7) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index fa935830..e2824a6f 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.6) + fc-vault-rails (0.7.7) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 2cbfc7e8..be1b24ab 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.6" + VERSION = "0.7.7" end end From d867df2bd0c94f485e15eb263aca33123fb75063 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Mon, 11 Mar 2019 09:06:54 +0000 Subject: [PATCH 075/143] Updates find_by and where searching for encrypted fields --- README.md | 18 +++++-- lib/vault/encrypted_model.rb | 34 ++++++++----- spec/integration/rails_spec.rb | 90 ++++++++++++++++++++++++++++++++-- 3 files changed, 123 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 5dd3c228..1563aaaa 100644 --- a/README.md +++ b/README.md @@ -294,15 +294,27 @@ Person.where(ssn: "123-45-6789") ``` That's why we have added a method that provides an easy to use search interface. Instead of using `.where` you can use -`.find_by_vault_attributes`. Example: +`.encrypted_where`. Example: ```ruby -Person.find_by_vault_attributes(driving_licence_number: '12345678') +Person.encrypted_where(driving_licence_number: '12345678') ``` This method will look up seamlessly in the relevant column with encrypted data. It is important to note that you can search only for attributes with **convergent** encryption. -Similar to `.where` the method `.find_by_vault_attributes` also returns an `ActiveRecord::Relation` +Similar to `.where` the method `.encrypted_where` also returns an `ActiveRecord::Relation` + +There is also `.encrypted_find_by` which works like `.find_by` finds the first encrypted record matching the specified conditions + +```ruby +Personal.encrypted_find_by(driving_licence_number: '12345678') +``` + +and `.encrypted_find_by!` like `encrypted_find_by`, except that if no record is found, raises an `ActiveRecord::RecordNotFound` error. + +```ruby +Personal.encrypted_find_by!(driving_licence_number: '12345678') +``` ### Uniqueness Validation If a column is **convergently** encrypted, it is possible to add a validation of uniqueness to it. diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 7cd3f247..8d3a4791 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -241,21 +241,33 @@ def encrypt_value(attribute, value) Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) end - def find_by_vault_attributes(attributes) - search_options = {} + def encrypted_find_by(attributes) + find_by(search_options(attributes)) + end - attributes.each do |attribute_name, attribute_value| - attribute_options = __vault_attributes[attribute_name] - encrypted_column = attribute_options[:encrypted_column] + def encrypted_find_by!(attributes) + find_by!(search_options(attributes)) + end - unless attribute_options[:convergent] - raise ArgumentError, 'You cannot search with non-convergent fields' - end + def encrypted_where(attributes) + where(search_options(attributes)) + end - search_options[encrypted_column] = encrypt_value(attribute_name, attribute_value) - end + private - where(search_options) + def search_options(attributes) + {}.tap do |search_options| + attributes.each do |attribute_name, attribute_value| + attribute_options = __vault_attributes[attribute_name] + encrypted_column = attribute_options[:encrypted_column] + + unless attribute_options[:convergent] + raise ArgumentError, 'You cannot search with non-convergent fields' + end + + search_options[encrypted_column] = encrypt_value(attribute_name, attribute_value) + end + end end end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 3ac364ab..6a1fa867 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -633,7 +633,87 @@ end end - describe '.find_by_vault_attributes' do + describe '.encrypted_find_by' do + before do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + it 'finds the expected record' do + first_person = LazyPerson.create!(passport_number: '12345678') + second_person = LazyPerson.create!(passport_number: '12345678') + third_person = LazyPerson.create!(passport_number: '87654321') + + expect(LazyPerson.encrypted_find_by(passport_number: '12345678')).to eq(first_person) + end + + context 'searching by attributes with defined serializer' do + it 'finds the expected record' do + first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) + second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + + expect(Person.encrypted_find_by(ip_address: IPAddr.new('127.0.0.1'))).to eq(first_person) + end + end + + context 'searching by multiple attributes' do + it 'finds the expected record' do + first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678') + + expect(Person.encrypted_find_by(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678')).to eq(first_person) + end + end + + context 'non-convergently encrypted attributes' do + it 'raises an exception' do + expect { LazyPerson.encrypted_find_by(ssn: '12345678') }.to raise_error('You cannot search with non-convergent fields') + end + end + end + + describe '.encrypted_find_by!' do + before do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + it 'finds the expected record' do + first_person = LazyPerson.create!(passport_number: '12345678') + second_person = LazyPerson.create!(passport_number: '12345678') + third_person = LazyPerson.create!(passport_number: '87654321') + + expect(LazyPerson.encrypted_find_by!(passport_number: '12345678')).to eq(first_person) + end + + context 'searching by attributes with defined serializer' do + it 'finds the expected record' do + first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) + second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + + expect(Person.encrypted_find_by!(ip_address: IPAddr.new('127.0.0.1'))).to eq(first_person) + end + end + + context 'searching by multiple attributes' do + it 'finds the expected record' do + first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678') + + expect(Person.encrypted_find_by!(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678')).to eq(first_person) + end + end + + context 'searching missing record' do + it 'raises an exception' do + expect { LazyPerson.encrypted_find_by!(passport_number: '000') }.to raise_exception(ActiveRecord::RecordNotFound) + end + end + + context 'non-convergently encrypted attributes' do + it 'raises an exception' do + expect { LazyPerson.encrypted_find_by!(ssn: '12345678') }.to raise_error('You cannot search with non-convergent fields') + end + end + end + + describe '.encrypted_where' do before do allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) end @@ -643,7 +723,7 @@ second_person = LazyPerson.create!(passport_number: '12345678') third_person = LazyPerson.create!(passport_number: '87654321') - expect(LazyPerson.find_by_vault_attributes(passport_number: '12345678').pluck(:id)).to match_array([first_person, second_person].map(&:id)) + expect(LazyPerson.encrypted_where(passport_number: '12345678').pluck(:id)).to match_array([first_person, second_person].map(&:id)) end context 'searching by attributes with defined serializer' do @@ -651,7 +731,7 @@ first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) - expect(Person.find_by_vault_attributes(ip_address: IPAddr.new('127.0.0.1')).pluck(:id)).to match_array([first_person.id]) + expect(Person.encrypted_where(ip_address: IPAddr.new('127.0.0.1')).pluck(:id)).to match_array([first_person.id]) end end @@ -659,13 +739,13 @@ it 'finds the expected records' do first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678') - expect(Person.find_by_vault_attributes(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678').pluck(:id)).to match_array([first_person.id]) + expect(Person.encrypted_where(ip_address: IPAddr.new('127.0.0.1'), driving_licence_number: '12345678').pluck(:id)).to match_array([first_person.id]) end end context 'non-convergently encrypted attributes' do it 'raises an exception' do - expect { LazyPerson.find_by_vault_attributes(ssn: '12345678') }.to raise_error('You cannot search with non-convergent fields') + expect { LazyPerson.encrypted_where(ssn: '12345678') }.to raise_error('You cannot search with non-convergent fields') end end end From 26c356c8e439b5176c7303ce4bb3ce13651caf8b Mon Sep 17 00:00:00 2001 From: Ahmet Date: Mon, 11 Mar 2019 09:08:08 +0000 Subject: [PATCH 076/143] Bump version to 1.0.0 --- CHANGELOG.md | 12 ++++++++++++ gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3639a6f6..3926f88f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ # Vault Rails Changelog +## 1.0.0 (March 8, 2019) + +NEW FEATURES +- Added `encrypted_find_by` finds the first encrypted record matching the specified conditions +- Added `encrypted_find_by!` like `encrypted_find_by`, except that if no record is found, raises an `ActiveRecord::RecordNotFound` error. + +IMPROVEMENTS +- `find_by_vault_attributes` renamed to `encrypted_where` as it returns a relation rather than a single record + +BREAKING CHANGES +- `find_by_vault_attributes` renamed to `encrypted_where` + ## 0.7.7 (March 6, 2019) IMPROVEMENTS diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 8e52a450..905f5433 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.7) + fc-vault-rails (1.0.0) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 8e5d9dae..7960ff7c 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.7) + fc-vault-rails (1.0.0) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index e2824a6f..6acf7c74 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (0.7.7) + fc-vault-rails (1.0.0) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index be1b24ab..1d09f2d3 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "0.7.7" + VERSION = "1.0.0" end end From 29dde57b84b3150d53507f5639e64e7eb3b24400 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Thu, 14 Mar 2019 13:38:11 +0000 Subject: [PATCH 077/143] Adds encrypted_where_not method --- CHANGELOG.md | 5 +++++ README.md | 4 +++- lib/vault/encrypted_model.rb | 4 ++++ spec/integration/rails_spec.rb | 14 ++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3926f88f..5558664c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Vault Rails Changelog +## 1.0.1 (March 14, 2019) + +NEW FEATURES +- Added `encrypted_where_not` finds encrypted records not matching the specified conditions + ## 1.0.0 (March 8, 2019) NEW FEATURES diff --git a/README.md b/README.md index 1563aaaa..41bf11f7 100644 --- a/README.md +++ b/README.md @@ -304,7 +304,9 @@ This method will look up seamlessly in the relevant column with encrypted data. It is important to note that you can search only for attributes with **convergent** encryption. Similar to `.where` the method `.encrypted_where` also returns an `ActiveRecord::Relation` -There is also `.encrypted_find_by` which works like `.find_by` finds the first encrypted record matching the specified conditions +Along with `.encrypted_where` we also have `.encrypted_where_not` which finds encrypted records not matching the specified conditions acts like `.where.not` + +There is also `.encrypted_find_by` which works like `.find_by` finds the first encrypted record matching the specified conditions. ```ruby Personal.encrypted_find_by(driving_licence_number: '12345678') diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index 8d3a4791..e8ee779a 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -253,6 +253,10 @@ def encrypted_where(attributes) where(search_options(attributes)) end + def encrypted_where_not(attributes) + where.not(search_options(attributes)) + end + private def search_options(attributes) diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 6a1fa867..b3f9e162 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -749,4 +749,18 @@ end end end + + describe '.encrypted_where_not' do + before do + allow(Vault::Rails).to receive(:convergent_encryption_context).and_return('a' * 16).at_least(:once) + end + + it 'finds the expected records' do + first_person = LazyPerson.create!(passport_number: '12345678') + second_person = LazyPerson.create!(passport_number: '12345678') + third_person = LazyPerson.create!(passport_number: '87654321') + + expect(LazyPerson.encrypted_where_not(passport_number: nil).pluck(:id)).to match_array([first_person, second_person, third_person].map(&:id)) + end + end end From f0569957e649d4cc9461ef530b1c03469e50dc5b Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Thu, 14 Mar 2019 13:38:53 +0000 Subject: [PATCH 078/143] Bump version to 1.0.1 --- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 905f5433..02ac8d75 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.0) + fc-vault-rails (1.0.1) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 7960ff7c..35158b35 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.0) + fc-vault-rails (1.0.1) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 6acf7c74..b6293eb3 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.0) + fc-vault-rails (1.0.1) activerecord (>= 5.0.0, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 1d09f2d3..244c74de 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,5 @@ module Vault module Rails - VERSION = "1.0.0" + VERSION = "1.0.1" end end From 0993db1105cf89d59ddbc6c4c91ed330bdf76da7 Mon Sep 17 00:00:00 2001 From: jdcalvin Date: Thu, 4 Apr 2019 15:13:13 -0700 Subject: [PATCH 079/143] Fix deprecation warning for Rails 5.1 Change was introduced here https://github.com/FundingCircle/fc-vault-rails/commit/bdb80c84a7bf0bb2c9101a6dd1c7316dd955666f to fix an incompatible change introduced by version rails 5.2. This change should apply to rails version 5.1 so deprecation warnings are not produced Also specified the rails version for the first migration as it is required in rails post 5.0. Specified 5.0 to match other migration files and reflect the gems supported versions of rails --- lib/vault/encrypted_model.rb | 4 ++-- spec/dummy/db/migrate/20150428220101_create_people.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index e8ee779a..e04d57cc 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -363,13 +363,13 @@ def __vault_encrypt_attribute!(attribute, options, in_after_save: false) # Only persist changed attributes to minimize requests - this helps # minimize the number of requests to Vault. - if in_after_save && ActiveRecord.version >= Gem::Version.new('5.2.0') + if in_after_save && ActiveRecord.version >= Gem::Version.new('5.1.0') # ActiveRecord 5.2 changes the behaviour of `changed` in `after_*` callbacks # https://www.ombulabs.com/blog/rails/upgrades/active-record-5-1-api-changes.html # https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-saved_change_to_attribute return unless saved_change_to_attribute?(attribute) else - # Rails >= 4.2.8 and <= 5.1 + # Rails >= 4.2.8 and < 5.1 return unless changed.include?("#{attribute}") end diff --git a/spec/dummy/db/migrate/20150428220101_create_people.rb b/spec/dummy/db/migrate/20150428220101_create_people.rb index 6979d887..1cef1818 100644 --- a/spec/dummy/db/migrate/20150428220101_create_people.rb +++ b/spec/dummy/db/migrate/20150428220101_create_people.rb @@ -1,4 +1,4 @@ -class CreatePeople < ActiveRecord::Migration +class CreatePeople < ActiveRecord::Migration[5.0] def change create_table :people do |t| t.string :name From 4fa817635206ddf63976da3396988d6ec675a16c Mon Sep 17 00:00:00 2001 From: Ahmet Date: Wed, 17 Apr 2019 12:04:07 +0100 Subject: [PATCH 080/143] Merge 0.6 and master branches to add support for Rails >= 4.2 --- .travis.yml | 1 + Appraisals | 3 + README.md | 13 +- fc-vault-rails.gemspec | 4 +- gemfiles/rails_4.2.gemfile | 7 + gemfiles/rails_4.2.gemfile.lock | 151 +++++++ gemfiles/rails_5.0.gemfile.lock | 6 +- gemfiles/rails_5.1.gemfile.lock | 6 +- gemfiles/rails_5.2.gemfile.lock | 6 +- lib/vault/encrypted_model.rb | 411 +---------------- lib/vault/latest/encrypted_model.rb | 412 ++++++++++++++++++ lib/vault/legacy/encrypted_model.rb | 391 +++++++++++++++++ lib/vault/perform_in_batches.rb | 8 +- lib/vault/rails.rb | 4 +- .../rails/serializers/string_serializer.rb | 17 + lib/vault/rails/version.rb | 4 + spec/dummy/app/models/lazy_person.rb | 3 + spec/dummy/app/models/person.rb | 7 +- ...81030234312_add_date_of_birth_to_people.rb | 6 + spec/dummy/db/schema.rb | 2 + spec/integration/rails_spec.rb | 20 +- spec/unit/attribute_proxy_spec.rb | 12 + spec/unit/encrypted_model_spec.rb | 50 ++- spec/unit/perform_in_batches_spec.rb | 52 ++- .../serializers/string_serializer_spec.rb | 17 + 25 files changed, 1145 insertions(+), 468 deletions(-) create mode 100644 gemfiles/rails_4.2.gemfile create mode 100644 gemfiles/rails_4.2.gemfile.lock create mode 100644 lib/vault/latest/encrypted_model.rb create mode 100644 lib/vault/legacy/encrypted_model.rb create mode 100644 lib/vault/rails/serializers/string_serializer.rb create mode 100644 spec/dummy/db/migrate/20181030234312_add_date_of_birth_to_people.rb create mode 100644 spec/unit/rails/serializers/string_serializer_spec.rb diff --git a/.travis.yml b/.travis.yml index 916c07f3..7c5d3399 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,6 +5,7 @@ sudo: false gemfile: - Gemfile + - gemfiles/rails_4.2.gemfile - gemfiles/rails_5.0.gemfile - gemfiles/rails_5.1.gemfile - gemfiles/rails_5.2.gemfile diff --git a/Appraisals b/Appraisals index c91f5abc..e511c9ce 100644 --- a/Appraisals +++ b/Appraisals @@ -1,3 +1,6 @@ +appraise "rails-4.2" do + gem "rails", "~> 4.2.0" +end appraise "rails-5.0" do gem "rails", "~> 5.0.0" end diff --git a/README.md b/README.md index 41bf11f7..5c8e6a05 100644 --- a/README.md +++ b/README.md @@ -336,18 +336,16 @@ In order to use this method for an attribute you need to add the following row i vault_attribute_proxy :attribute, :attribute_ciphertext, encrypted_attribute_only: true ``` -Upgrading to 0.7 from 0.6 +Upgrading to the latest version from 0.6.x ------------------------- -Version 0.6 targets rails 4.x and 0.7 targets rails 5.x. There are breaking changes between the two versions too, so upgrading isn't as smooth as it could be. +Master now targets both rails 4.2.x and rails 5.x. There are breaking changes between the two versions too, so upgrading isn't as smooth as it could be. -1. You no longer need to `include Vault::AttributeProxy` to get `vault_attribute_proxy` as it is part of `Vault::EncryptedModel` now. In 0.7.0 the `Vault::AttributeProxy` module isn't part of the gem, but from 0.7.1+ it exists just to emit a deprecation warning reminding you to stop including it. +1. You no longer need to `include Vault::AttributeProxy` to get `vault_attribute_proxy` as it is part of `Vault::EncryptedModel` now in both versions. - **If you do nothing** your app will still work properly in 0.7.1+, but you'll get annoying messages. + **If you do nothing** your app will still work properly in master, but you'll get annoying messages. -2. The position of the `type` parameter has changed from `vault_attribute_proxy` in 0.6 to `vault_attribute` in 0.7. In 0.7 if you have a `type` option on `vault_attribute_proxy` it will emit a deprecation warning reminding you to move the definition onto `vault_attribute`. - - **If you do nothing** your app will likely break because `fc-vault-rails` will assume your `vault_attribute` is just a string and if you had `type` on `vault_attribute_proxy` it's likely not. +2. Passing a type as an object is no longer supported `type: ActiveRecord::Type::Time.new` if you need to use a `ActiveRecord::Type` pass it as a symbol e.g. `type: :time`. Development ----------- @@ -361,6 +359,7 @@ Important Notes: - **The tests must be be idempotent.** The HTTP calls made during a test should be able to be run over and over. - **Tests are order independent.** The default RSpec configuration randomizes the test order, so this should not be a problem. +We now have two versions of `Vault::EncryptedModel` a `Latest` version which targets rails 5.x and up and a `Legacy` version which targets 4.2.x. It made sense to keep these two version seperate from one another because of the amount of differences between them. So if changes need to be applied to support both versions both files must be changed. Getting tests to run -------------------- diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 2dac1af3..9659e139 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,12 +17,12 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", [">= 5.0.0", "< 6.0"] + s.add_dependency "activerecord", [">= 4.2", "< 6.0"] s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", [">= 5.0.0", "< 6.0"] + s.add_development_dependency "rails", [">= 4.2", "< 6.0"] s.add_development_dependency "byebug" s.add_development_dependency "pry" s.add_development_dependency "rake", "~> 10.0" diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile new file mode 100644 index 00000000..6977eb02 --- /dev/null +++ b/gemfiles/rails_4.2.gemfile @@ -0,0 +1,7 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 4.2.0" + +gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock new file mode 100644 index 00000000..77ba481c --- /dev/null +++ b/gemfiles/rails_4.2.gemfile.lock @@ -0,0 +1,151 @@ +PATH + remote: .. + specs: + fc-vault-rails (1.0.1) + activerecord (>= 4.2, < 6.0) + vault (~> 0.7) + +GEM + remote: https://rubygems.org/ + specs: + actionmailer (4.2.11.1) + actionpack (= 4.2.11.1) + actionview (= 4.2.11.1) + activejob (= 4.2.11.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 1.0, >= 1.0.5) + actionpack (4.2.11.1) + actionview (= 4.2.11.1) + activesupport (= 4.2.11.1) + rack (~> 1.6) + rack-test (~> 0.6.2) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (4.2.11.1) + activesupport (= 4.2.11.1) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 1.0, >= 1.0.5) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (4.2.11.1) + activesupport (= 4.2.11.1) + globalid (>= 0.3.0) + activemodel (4.2.11.1) + activesupport (= 4.2.11.1) + builder (~> 3.1) + activerecord (4.2.11.1) + activemodel (= 4.2.11.1) + activesupport (= 4.2.11.1) + arel (~> 6.0) + activesupport (4.2.11.1) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + arel (6.0.4) + aws-eventstream (1.0.2) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) + builder (3.2.3) + byebug (11.0.1) + coderay (1.1.2) + concurrent-ruby (1.1.5) + crass (1.0.4) + diff-lcs (1.3) + erubis (2.7.0) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + method_source (0.9.2) + mini_mime (1.0.1) + mini_portile2 (2.4.0) + minitest (5.11.3) + nokogiri (1.10.2) + mini_portile2 (~> 2.4.0) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rack (1.6.11) + rack-test (0.6.3) + rack (>= 1.0) + rails (4.2.11.1) + actionmailer (= 4.2.11.1) + actionpack (= 4.2.11.1) + actionview (= 4.2.11.1) + activejob (= 4.2.11.1) + activemodel (= 4.2.11.1) + activerecord (= 4.2.11.1) + activesupport (= 4.2.11.1) + bundler (>= 1.3.0, < 2.0) + railties (= 4.2.11.1) + sprockets-rails + rails-deprecated_sanitizer (1.0.3) + activesupport (>= 4.2.0.alpha) + rails-dom-testing (1.0.9) + activesupport (>= 4.2.0, < 5.0) + nokogiri (~> 1.6) + rails-deprecated_sanitizer (>= 1.0.1) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + railties (4.2.11.1) + actionpack (= 4.2.11.1) + activesupport (= 4.2.11.1) + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (10.5.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-support (3.8.0) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + vault (0.12.0) + aws-sigv4 + wwtd (1.3.0) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.1) + bundler + byebug + fc-vault-rails! + pry + rails (~> 4.2.0) + rake (~> 10.0) + rspec (~> 3.2) + sqlite3 (~> 1.3.6) + wwtd + +BUNDLED WITH + 1.17.2 diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 02ac8d75..e71467a0 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (1.0.1) - activerecord (>= 5.0.0, < 6.0) + activerecord (>= 4.2, < 6.0) vault (~> 0.7) GEM @@ -50,7 +50,9 @@ GEM rake thor (>= 0.14.0) arel (7.1.4) - aws-sigv4 (1.0.3) + aws-eventstream (1.0.2) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) byebug (11.0.0) coderay (1.1.2) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 35158b35..736e10eb 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (1.0.1) - activerecord (>= 5.0.0, < 6.0) + activerecord (>= 4.2, < 6.0) vault (~> 0.7) GEM @@ -50,7 +50,9 @@ GEM rake thor (>= 0.14.0) arel (8.0.0) - aws-sigv4 (1.0.3) + aws-eventstream (1.0.2) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) byebug (11.0.0) coderay (1.1.2) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index b6293eb3..47de987d 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (1.0.1) - activerecord (>= 5.0.0, < 6.0) + activerecord (>= 4.2, < 6.0) vault (~> 0.7) GEM @@ -54,7 +54,9 @@ GEM rake thor (>= 0.14.0) arel (9.0.0) - aws-sigv4 (1.0.3) + aws-eventstream (1.0.2) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) byebug (11.0.0) coderay (1.1.2) diff --git a/lib/vault/encrypted_model.rb b/lib/vault/encrypted_model.rb index e04d57cc..1038f5ee 100644 --- a/lib/vault/encrypted_model.rb +++ b/lib/vault/encrypted_model.rb @@ -1,410 +1,11 @@ -require "active_support/concern" require "active_record" -require "active_record/type" +require_relative "latest/encrypted_model" +require_relative "legacy/encrypted_model" module Vault - module EncryptedModel - extend ActiveSupport::Concern - - module ClassMethods - # Creates an attribute that is read and written using Vault. - # - # @example - # - # class Person < ActiveRecord::Base - # include Vault::EncryptedModel - # vault_attribute :ssn - # end - # - # person = Person.new - # person.ssn = "123-45-6789" - # person.save - # person.encrypted_ssn #=> "vault:v0:6hdPkhvyL6..." - # - # @param [Symbol] column - # the column that is encrypted - # @param [Hash] options - # - # @option options [Symbol] :encrypted_column - # the name of the encrypted column (default: +#{column}_encrypted+) - # @option options [Bool] :convergent - # use convergent encryption (default: +false+) - # @option options [String] :path - # the path to the transit backend (default: +transit+) - # @option options [String] :key - # the name of the encryption key (default: +#{app}_#{table}_#{column}+) - # @option options [Symbol, Class] :serializer - # the name of the serializer to use (or a class) - # @option options [Proc] :encode - # a proc to encode the value with - # @option options [Proc] :decode - # a proc to decode the value with - def vault_attribute(attribute_name, options = {}) - encrypted_column = options[:encrypted_column] || "#{attribute_name}_encrypted" - path = options[:path] || "transit" - key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute_name}" - convergent = options.fetch(:convergent, false) - - # Sanity check options! - _vault_validate_options!(options) - - attribute_type = _vault_fetch_attribute_type(options) - - # Attribute API - attribute(attribute_name, attribute_type) - - # Getter - define_method(attribute_name) do - unless __vault_loaded_attributes.include?(attribute_name) - __vault_load_attribute!(attribute_name, self.class.__vault_attributes[attribute_name]) - end - - read_attribute(attribute_name) - end - - # Setter - define_method("#{attribute_name}=") do |value| - # Prevent the attribute from loading when a value is provided before - # the attribute is loaded from Vault but only if the model is initialized - __vault_loaded_attributes << attribute_name - - # Force the update of the attribute, to be consistent with old behaviour - cast_value = write_attribute(attribute_name, value) - - # Rails 4.2 resets the dirty state if write_attribute is called with the same value after attribute_will_change - attribute_will_change!(attribute_name) - - cast_value - end - - serializer = _vault_fetch_serializer(options, attribute_type) - - # Make a note of this attribute so we can use it in the future (maybe). - __vault_attributes[attribute_name.to_sym] = { - key: key, - path: path, - serializer: serializer, - encrypted_column: encrypted_column, - convergent: convergent - } - - self - end - - # Encrypt Vault attributes before saving them - def vault_persist_before_save! - skip_callback :save, :after, :__vault_persist_attributes! - before_save :__vault_encrypt_attributes! - end - - # Define proxy getter and setter methods - # - # Override the getter and setter for a particular non-encrypted attribute - # so that they also call the getter/setter of the encrypted one. - # This ensures that all the code that uses the attribute in question - # also updates/retrieves the encrypted value whenever it is available. - # - # This method is useful if you have a plaintext attribute that you want to replace with a vault attribute. - # During a transition period both attributes can be seamlessly read/changed at the same time. - # - # @param [String | Symbol] non_encrypted_attribute - # The name of original attribute (non-encrypted). - # @param [String | Symbol] encrypted_attribute - # The name of the encrypted attribute. - # This makes sure that the encrypted attribute behaves like a real AR attribute. - # @param [Boolean] (false) encrypted_attribute_only - # Whether to read and write to both encrypted and non-encrypted attributes. - # Useful for when we stop using the non-encrypted one. - def vault_attribute_proxy(non_encrypted_attribute, encrypted_attribute, options={}) - if options[:type].present? - ActiveSupport::Deprecation.warn('The `type` option on `vault_attribute_proxy` is now ignored. To specify type information you should move the `type` option onto the `vault_attribute` definition.') - end - # Only return the encrypted attribute if it's available and encrypted_attribute_only is true. - define_method(non_encrypted_attribute) do - return send(encrypted_attribute) if options[:encrypted_attribute_only] - - send(encrypted_attribute) || super() - end - - # Update only the encrypted attribute if encrypted_attribute_only is true and both attributes otherwise. - define_method("#{non_encrypted_attribute}=") do |value| - super(value) unless options[:encrypted_attribute_only] - - send("#{encrypted_attribute}=", value) - end - end - - # The list of Vault attributes. - # - # @return [Hash] - def __vault_attributes - @vault_attributes ||= {} - end - - # Validate that Vault options are all a-okay! This method will raise - # exceptions if something does not make sense. - def _vault_validate_options!(options) - if options[:serializer] - if options[:encode] || options[:decode] - raise Vault::Rails::ValidationFailedError, "Cannot use a " \ - "custom encoder/decoder if a `:serializer' is specified!" - end - end - - if options[:encode] && !options[:decode] - raise Vault::Rails::ValidationFailedError, "Cannot specify " \ - "`:encode' without specifying `:decode' as well!" - end - - if options[:decode] && !options[:encode] - raise Vault::Rails::ValidationFailedError, "Cannot specify " \ - "`:decode' without specifying `:encode' as well!" - end - end - - def _vault_fetch_attribute_type(options) - attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) - - if attribute_type.is_a?(Symbol) - ActiveRecord::Type.lookup(attribute_type) - else - attribute_type - end - rescue ArgumentError => e - if e.message =~ /Unknown type / - raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" - else - raise - end - end - - def _vault_fetch_serializer(options, attribute_type) - if options[:serialize] - serializer = options[:serialize] - - # Unless a class or module was given, construct our serializer. (Slass - # is a subset of Module). - if serializer && !serializer.is_a?(Module) - Vault::Rails.serializer_for(serializer) - else - serializer - end - elsif options[:encode] && options[:decode] - # See if custom encoding or decoding options were given. - Class.new do - define_singleton_method(:encode, &options[:encode]) - define_singleton_method(:decode, &options[:decode]) - end - elsif attribute_type.is_a?(ActiveRecord::Type::Value) && attribute_type.type.present? - begin - Vault::Rails.serializer_for(attribute_type.type) - rescue Vault::Rails::Serializers::UnknownSerializerError - nil - end - end - end - - def vault_lazy_decrypt? - !!@vault_lazy_decrypt - end - - def vault_lazy_decrypt! - @vault_lazy_decrypt = true - end - - # works only with convergent encryption - def vault_persist_all(attribute, records, plaintexts, validate: true) - options = __vault_attributes[attribute] - - Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts, validate: validate) - end - - # works only with convergent encryption - # relevant only if lazy decryption is enabled - def vault_load_all(attribute, records) - options = __vault_attributes[attribute] - - Vault::PerformInBatches.new(attribute, options).decrypt(records) - end - - def encrypt_value(attribute, value) - options = __vault_attributes[attribute] - - key = options[:key] - path = options[:path] - serializer = options[:serializer] - convergent = options[:convergent] - - # Apply the serializer to the value, if one exists - plaintext = serializer ? serializer.encode(value) : value - - Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) - end - - def encrypted_find_by(attributes) - find_by(search_options(attributes)) - end - - def encrypted_find_by!(attributes) - find_by!(search_options(attributes)) - end - - def encrypted_where(attributes) - where(search_options(attributes)) - end - - def encrypted_where_not(attributes) - where.not(search_options(attributes)) - end - - private - - def search_options(attributes) - {}.tap do |search_options| - attributes.each do |attribute_name, attribute_value| - attribute_options = __vault_attributes[attribute_name] - encrypted_column = attribute_options[:encrypted_column] - - unless attribute_options[:convergent] - raise ArgumentError, 'You cannot search with non-convergent fields' - end - - search_options[encrypted_column] = encrypt_value(attribute_name, attribute_value) - end - end - end - end - - included do - # After a resource has been initialized, immediately communicate with - # Vault and decrypt any attributes unless vault_lazy_decrypt is set. - after_initialize :__vault_initialize_attributes! - - # After we save the record, persist all the values to Vault and reload - # them attributes from Vault to ensure we have the proper attributes set. - # The reason we use `after_save` here is because a `before_save` could - # run too early in the callback process. If a user is changing Vault - # attributes in a callback, it is possible that our callback will run - # before theirs, resulting in attributes that are not persisted. - after_save :__vault_persist_attributes! - - def __vault_loaded_attributes - @__vault_loaded_attributes ||= Set.new - end - - def __vault_initialize_attributes! - return if self.class.vault_lazy_decrypt? - - __vault_load_attributes! - end - - # Decrypt all the attributes from Vault. - def __vault_load_attributes! - self.class.__vault_attributes.each do |attribute, options| - self.__vault_load_attribute!(attribute, options) - end - end - - # Decrypt and load a single attribute from Vault. - def __vault_load_attribute!(attribute, options) - # If the user provided a value for the attribute, do not try to load it from Vault - return if __vault_loaded_attributes.include?(attribute) - - key = options[:key] - path = options[:path] - serializer = options[:serializer] - column = options[:encrypted_column] - convergent = options[:convergent] - - # Load the ciphertext - ciphertext = read_attribute(column) - - # Load the plaintext value - plaintext = Vault::Rails.decrypt(path, key, ciphertext, Vault.client, convergent) - - # Deserialize the plaintext value, if a serializer exists - plaintext = serializer.decode(plaintext) if serializer - - __vault_loaded_attributes << attribute - - # Write the virtual attribute with the plaintext value - write_attribute(attribute, plaintext) - end - - # Encrypt all the attributes using Vault and set the encrypted values back - # on this model. - # @return [true] - def __vault_persist_attributes! - changes = __vault_encrypt_attributes!(in_after_save: true) - - # If there are any changes to the model, update them all at once, - # skipping any callbacks and validation. This is okay, because we are - # already in a transaction due to the callback. - self.update_columns(changes) unless changes.empty? - - true - end - - def __vault_encrypt_attributes!(in_after_save: false) - changes = {} - - self.class.__vault_attributes.each do |attribute, options| - if c = self.__vault_encrypt_attribute!(attribute, options, in_after_save: in_after_save) - changes.merge!(c) - end - end - - changes - end - - # Encrypt a single attribute using Vault and persist back onto the - # encrypted attribute value. - def __vault_encrypt_attribute!(attribute, options, in_after_save: false) - # Only persist changed attributes to minimize requests - this helps - # minimize the number of requests to Vault. - - if in_after_save && ActiveRecord.version >= Gem::Version.new('5.1.0') - # ActiveRecord 5.2 changes the behaviour of `changed` in `after_*` callbacks - # https://www.ombulabs.com/blog/rails/upgrades/active-record-5-1-api-changes.html - # https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-saved_change_to_attribute - return unless saved_change_to_attribute?(attribute) - else - # Rails >= 4.2.8 and < 5.1 - return unless changed.include?("#{attribute}") - end - - column = options[:encrypted_column] - - # Get the current value of the plaintext attribute - plaintext = read_attribute(attribute) - - # Generate the ciphertext and store it back as an attribute - ciphertext = self.class.encrypt_value(attribute, plaintext) - - # Write the attribute back, so that we don't have to reload the record - # to get the ciphertext - write_attribute(column, ciphertext) - - # Return the updated column so we can save - { column => ciphertext } - end - - # Override the reload method to reload the Vault attributes. This will - # ensure that we always have the most recent data from Vault when we - # reload a record from the database. - def reload(*) - super.tap do - # Unset all attributes to force the new data to be pulled from Vault - self.class.__vault_attributes.each do |attribute, _| - write_attribute(attribute, nil) - end - - __vault_loaded_attributes.clear - - __vault_initialize_attributes! - clear_changes_information - end - end - end + EncryptedModel = if Vault::Rails.latest? + Latest::EncryptedModel + else + Legacy::EncryptedModel end end diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb new file mode 100644 index 00000000..4a82e239 --- /dev/null +++ b/lib/vault/latest/encrypted_model.rb @@ -0,0 +1,412 @@ +require "active_support/concern" +require "active_record" +require "active_record/type" + +module Vault + module Latest + module EncryptedModel + extend ActiveSupport::Concern + + module ClassMethods + # Creates an attribute that is read and written using Vault. + # + # @example + # + # class Person < ActiveRecord::Base + # include Vault::EncryptedModel + # vault_attribute :ssn + # end + # + # person = Person.new + # person.ssn = "123-45-6789" + # person.save + # person.encrypted_ssn #=> "vault:v0:6hdPkhvyL6..." + # + # @param [Symbol] column + # the column that is encrypted + # @param [Hash] options + # + # @option options [Symbol] :encrypted_column + # the name of the encrypted column (default: +#{column}_encrypted+) + # @option options [Bool] :convergent + # use convergent encryption (default: +false+) + # @option options [String] :path + # the path to the transit backend (default: +transit+) + # @option options [String] :key + # the name of the encryption key (default: +#{app}_#{table}_#{column}+) + # @option options [Symbol, Class] :serializer + # the name of the serializer to use (or a class) + # @option options [Proc] :encode + # a proc to encode the value with + # @option options [Proc] :decode + # a proc to decode the value with + def vault_attribute(attribute_name, options = {}) + encrypted_column = options[:encrypted_column] || "#{attribute_name}_encrypted" + path = options[:path] || "transit" + key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute_name}" + convergent = options.fetch(:convergent, false) + + # Sanity check options! + _vault_validate_options!(options) + + attribute_type = _vault_fetch_attribute_type(options) + + # Attribute API + attribute(attribute_name, attribute_type) + + # Getter + define_method(attribute_name) do + unless __vault_loaded_attributes.include?(attribute_name) + __vault_load_attribute!(attribute_name, self.class.__vault_attributes[attribute_name]) + end + + read_attribute(attribute_name) + end + + # Setter + define_method("#{attribute_name}=") do |value| + # Prevent the attribute from loading when a value is provided before + # the attribute is loaded from Vault but only if the model is initialized + __vault_loaded_attributes << attribute_name + + # Force the update of the attribute, to be consistent with old behaviour + cast_value = write_attribute(attribute_name, value) + + # Rails 4.2 resets the dirty state if write_attribute is called with the same value after attribute_will_change + attribute_will_change!(attribute_name) + + cast_value + end + + serializer = _vault_fetch_serializer(options, attribute_type) + + # Make a note of this attribute so we can use it in the future (maybe). + __vault_attributes[attribute_name.to_sym] = { + key: key, + path: path, + serializer: serializer, + encrypted_column: encrypted_column, + convergent: convergent + } + + self + end + + # Encrypt Vault attributes before saving them + def vault_persist_before_save! + skip_callback :save, :after, :__vault_persist_attributes! + before_save :__vault_encrypt_attributes! + end + + # Define proxy getter and setter methods + # + # Override the getter and setter for a particular non-encrypted attribute + # so that they also call the getter/setter of the encrypted one. + # This ensures that all the code that uses the attribute in question + # also updates/retrieves the encrypted value whenever it is available. + # + # This method is useful if you have a plaintext attribute that you want to replace with a vault attribute. + # During a transition period both attributes can be seamlessly read/changed at the same time. + # + # @param [String | Symbol] non_encrypted_attribute + # The name of original attribute (non-encrypted). + # @param [String | Symbol] encrypted_attribute + # The name of the encrypted attribute. + # This makes sure that the encrypted attribute behaves like a real AR attribute. + # @param [Boolean] (false) encrypted_attribute_only + # Whether to read and write to both encrypted and non-encrypted attributes. + # Useful for when we stop using the non-encrypted one. + def vault_attribute_proxy(non_encrypted_attribute, encrypted_attribute, options={}) + if options[:type].present? + ActiveSupport::Deprecation.warn('The `type` option on `vault_attribute_proxy` is now ignored. To specify type information you should move the `type` option onto the `vault_attribute` definition.') + end + # Only return the encrypted attribute if it's available and encrypted_attribute_only is true. + define_method(non_encrypted_attribute) do + return send(encrypted_attribute) if options[:encrypted_attribute_only] + + send(encrypted_attribute) || super() + end + + # Update only the encrypted attribute if encrypted_attribute_only is true and both attributes otherwise. + define_method("#{non_encrypted_attribute}=") do |value| + super(value) unless options[:encrypted_attribute_only] + + send("#{encrypted_attribute}=", value) + end + end + + # The list of Vault attributes. + # + # @return [Hash] + def __vault_attributes + @vault_attributes ||= {} + end + + # Validate that Vault options are all a-okay! This method will raise + # exceptions if something does not make sense. + def _vault_validate_options!(options) + if options[:serializer] + if options[:encode] || options[:decode] + raise Vault::Rails::ValidationFailedError, "Cannot use a " \ + "custom encoder/decoder if a `:serializer' is specified!" + end + end + + if options[:encode] && !options[:decode] + raise Vault::Rails::ValidationFailedError, "Cannot specify " \ + "`:encode' without specifying `:decode' as well!" + end + + if options[:decode] && !options[:encode] + raise Vault::Rails::ValidationFailedError, "Cannot specify " \ + "`:decode' without specifying `:encode' as well!" + end + end + + def _vault_fetch_attribute_type(options) + attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) + + if attribute_type.is_a?(Symbol) + ActiveRecord::Type.lookup(attribute_type) + else + ActiveModel::Type::Value.new + end + rescue ArgumentError => e + if e.message =~ /Unknown type / + raise RuntimeError, "Unrecognized attribute type `#{attribute_type}`!" + else + raise + end + end + + def _vault_fetch_serializer(options, attribute_type) + if options[:serialize] + serializer = options[:serialize] + + # Unless a class or module was given, construct our serializer. (Slass + # is a subset of Module). + if serializer && !serializer.is_a?(Module) + Vault::Rails.serializer_for(serializer) + else + serializer + end + elsif options[:encode] && options[:decode] + # See if custom encoding or decoding options were given. + Class.new do + define_singleton_method(:encode, &options[:encode]) + define_singleton_method(:decode, &options[:decode]) + end + elsif attribute_type.is_a?(ActiveRecord::Type::Value) && attribute_type.type.present? + begin + Vault::Rails.serializer_for(attribute_type.type) + rescue Vault::Rails::Serializers::UnknownSerializerError + nil + end + end + end + + def vault_lazy_decrypt? + !!@vault_lazy_decrypt + end + + def vault_lazy_decrypt! + @vault_lazy_decrypt = true + end + + # works only with convergent encryption + def vault_persist_all(attribute, records, plaintexts, validate: true) + options = __vault_attributes[attribute] + + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts, validate: validate) + end + + # works only with convergent encryption + # relevant only if lazy decryption is enabled + def vault_load_all(attribute, records) + options = __vault_attributes[attribute] + + Vault::PerformInBatches.new(attribute, options).decrypt(records) + end + + def encrypt_value(attribute, value) + options = __vault_attributes[attribute] + + key = options[:key] + path = options[:path] + serializer = options[:serializer] + convergent = options[:convergent] + + # Apply the serializer to the value, if one exists + plaintext = serializer ? serializer.encode(value) : value + + Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) + end + + def encrypted_find_by(attributes) + find_by(search_options(attributes)) + end + + def encrypted_find_by!(attributes) + find_by!(search_options(attributes)) + end + + def encrypted_where(attributes) + where(search_options(attributes)) + end + + def encrypted_where_not(attributes) + where.not(search_options(attributes)) + end + + private + + def search_options(attributes) + {}.tap do |search_options| + attributes.each do |attribute_name, attribute_value| + attribute_options = __vault_attributes[attribute_name] + encrypted_column = attribute_options[:encrypted_column] + + unless attribute_options[:convergent] + raise ArgumentError, 'You cannot search with non-convergent fields' + end + + search_options[encrypted_column] = encrypt_value(attribute_name, attribute_value) + end + end + end + end + + included do + # After a resource has been initialized, immediately communicate with + # Vault and decrypt any attributes unless vault_lazy_decrypt is set. + after_initialize :__vault_initialize_attributes! + + # After we save the record, persist all the values to Vault and reload + # them attributes from Vault to ensure we have the proper attributes set. + # The reason we use `after_save` here is because a `before_save` could + # run too early in the callback process. If a user is changing Vault + # attributes in a callback, it is possible that our callback will run + # before theirs, resulting in attributes that are not persisted. + after_save :__vault_persist_attributes! + + def __vault_loaded_attributes + @__vault_loaded_attributes ||= Set.new + end + + def __vault_initialize_attributes! + return if self.class.vault_lazy_decrypt? + + __vault_load_attributes! + end + + # Decrypt all the attributes from Vault. + def __vault_load_attributes! + self.class.__vault_attributes.each do |attribute, options| + self.__vault_load_attribute!(attribute, options) + end + end + + # Decrypt and load a single attribute from Vault. + def __vault_load_attribute!(attribute, options) + # If the user provided a value for the attribute, do not try to load it from Vault + return if __vault_loaded_attributes.include?(attribute) + + key = options[:key] + path = options[:path] + serializer = options[:serializer] + column = options[:encrypted_column] + convergent = options[:convergent] + + # Load the ciphertext + ciphertext = read_attribute(column) + + # Load the plaintext value + plaintext = Vault::Rails.decrypt(path, key, ciphertext, Vault.client, convergent) + + # Deserialize the plaintext value, if a serializer exists + plaintext = serializer.decode(plaintext) if serializer + + __vault_loaded_attributes << attribute + + # Write the virtual attribute with the plaintext value + write_attribute(attribute, plaintext) + end + + # Encrypt all the attributes using Vault and set the encrypted values back + # on this model. + # @return [true] + def __vault_persist_attributes! + changes = __vault_encrypt_attributes!(in_after_save: true) + + # If there are any changes to the model, update them all at once, + # skipping any callbacks and validation. This is okay, because we are + # already in a transaction due to the callback. + self.update_columns(changes) unless changes.empty? + + true + end + + def __vault_encrypt_attributes!(in_after_save: false) + changes = {} + + self.class.__vault_attributes.each do |attribute, options| + if c = self.__vault_encrypt_attribute!(attribute, options, in_after_save: in_after_save) + changes.merge!(c) + end + end + + changes + end + + # Encrypt a single attribute using Vault and persist back onto the + # encrypted attribute value. + def __vault_encrypt_attribute!(attribute, options, in_after_save: false) + # Only persist changed attributes to minimize requests - this helps + # minimize the number of requests to Vault. + + if in_after_save && ActiveRecord.version >= Gem::Version.new('5.1.0') + # ActiveRecord 5.2 changes the behaviour of `changed` in `after_*` callbacks + # https://www.ombulabs.com/blog/rails/upgrades/active-record-5-1-api-changes.html + # https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Dirty.html#method-i-saved_change_to_attribute + return unless saved_change_to_attribute?(attribute) + else + # Rails >= 4.2.8 and < 5.1 + return unless changed.include?("#{attribute}") + end + + column = options[:encrypted_column] + + # Get the current value of the plaintext attribute + plaintext = read_attribute(attribute) + + # Generate the ciphertext and store it back as an attribute + ciphertext = self.class.encrypt_value(attribute, plaintext) + + # Write the attribute back, so that we don't have to reload the record + # to get the ciphertext + write_attribute(column, ciphertext) + + # Return the updated column so we can save + { column => ciphertext } + end + + # Override the reload method to reload the Vault attributes. This will + # ensure that we always have the most recent data from Vault when we + # reload a record from the database. + def reload(*) + super.tap do + # Unset all attributes to force the new data to be pulled from Vault + self.class.__vault_attributes.each do |attribute, _| + write_attribute(attribute, nil) + end + + __vault_loaded_attributes.clear + + __vault_initialize_attributes! + clear_changes_information + end + end + end + end + end +end diff --git a/lib/vault/legacy/encrypted_model.rb b/lib/vault/legacy/encrypted_model.rb new file mode 100644 index 00000000..ea40060d --- /dev/null +++ b/lib/vault/legacy/encrypted_model.rb @@ -0,0 +1,391 @@ +require "active_support/concern" + +module Vault + module Legacy + module EncryptedModel + extend ActiveSupport::Concern + + module ClassMethods + # Creates an attribute that is read and written using Vault. + # + # @example + # + # class Person < ActiveRecord::Base + # include Vault::EncryptedModel + # vault_attribute :ssn + # end + # + # person = Person.new + # person.ssn = "123-45-6789" + # person.save + # person.encrypted_ssn #=> "vault:v0:6hdPkhvyL6..." + # + # @param [Symbol] column + # the column that is encrypted + # @param [Hash] options + # + # @option options [Symbol] :encrypted_column + # the name of the encrypted column (default: +#{column}_encrypted+) + # @option options [Bool] :convergent + # use convergent encryption (default: +false+) + # @option options [String] :path + # the path to the transit backend (default: +transit+) + # @option options [String] :key + # the name of the encryption key (default: +#{app}_#{table}_#{column}+) + # @option options [Symbol, Class] :serializer + # the name of the serializer to use (or a class) + # @option options [Proc] :encode + # a proc to encode the value with + # @option options [Proc] :decode + # a proc to decode the value with + def vault_attribute(attribute, options = {}) + encrypted_column = options[:encrypted_column] || "#{attribute}_encrypted" + path = options[:path] || "transit" + key = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute}" + convergent = options.fetch(:convergent, false) + + # Sanity check options! + _vault_validate_options!(options) + + # Get the serializer if one was given. + serializer = options[:serialize] + + # Unless a class or module was given, construct our serializer. (Slass + # is a subset of Module). + if serializer && !serializer.is_a?(Module) + serializer = Vault::Rails.serializer_for(serializer) + end + + # See if custom encoding or decoding options were given. + if options[:encode] && options[:decode] + serializer = Class.new + serializer.define_singleton_method(:encode, &options[:encode]) + serializer.define_singleton_method(:decode, &options[:decode]) + end + + unless serializer + serializer = Vault::Rails::Serializers::StringSerializer + end + + # Getter + define_method("#{attribute}") do + if instance_variable_defined?("@#{attribute}") + return instance_variable_get("@#{attribute}") + end + + __vault_load_attribute!(attribute, self.class.__vault_attributes[attribute]) + end + + # Setter + define_method("#{attribute}=") do |value| + cast_value = cast_value_to_type(options, value) + # We always set it as changed without comparing with the current value + # because we allow our held values to be mutated, so we need to assume + # that if you call attr=, you want it send back regardless. + attribute_will_change!("#{attribute}") + instance_variable_set("@#{attribute}", cast_value) + end + + # Checker + define_method("#{attribute}?") do + send("#{attribute}").present? + end + + # Dirty method + define_method("#{attribute}_change") do + changes["#{attribute}"] + end + + # Dirty method + define_method("#{attribute}_changed?") do + changed.include?("#{attribute}") + end + + # Dirty method + define_method("#{attribute}_was") do + if changes["#{attribute}"] + changes["#{attribute}"][0] + else + public_send("#{attribute}") + end + end + + # Make a note of this attribute so we can use it in the future (maybe). + __vault_attributes[attribute.to_sym] = { + key: key, + path: path, + serializer: serializer, + encrypted_column: encrypted_column, + convergent: convergent + } + + self + end + + # Encrypt Vault attributes before saving them + def vault_persist_before_save! + skip_callback :save, :after, :__vault_persist_attributes! + before_save :__vault_encrypt_attributes! + end + + # Define proxy getter and setter methods + # + # Override the getter and setter for a particular non-encrypted attribute + # so that they also call the getter/setter of the encrypted one. + # This ensures that all the code that uses the attribute in question + # also updates/retrieves the encrypted value whenever it is available. + # + # This method is useful if you have a plaintext attribute that you want to replace with a vault attribute. + # During a transition period both attributes can be seamlessly read/changed at the same time. + # + # @param [String | Symbol] non_encrypted_attribute + # The name of original attribute (non-encrypted). + # @param [String | Symbol] encrypted_attribute + # The name of the encrypted attribute. + # This makes sure that the encrypted attribute behaves like a real AR attribute. + # @param [Boolean] (false) encrypted_attribute_only + # Whether to read and write to both encrypted and non-encrypted attributes. + # Useful for when we stop using the non-encrypted one. + def vault_attribute_proxy(non_encrypted_attribute, encrypted_attribute, options={}) + if options[:type].present? + ActiveSupport::Deprecation.warn('The `type` option on `vault_attribute_proxy` is now ignored. To specify type information you should move the `type` option onto the `vault_attribute` definition.') + end + # Only return the encrypted attribute if it's available and encrypted_attribute_only is true. + define_method(non_encrypted_attribute) do + return send(encrypted_attribute) if options[:encrypted_attribute_only] + + send(encrypted_attribute) || super() + end + + # Update only the encrypted attribute if encrypted_attribute_only is true and both attributes otherwise. + define_method("#{non_encrypted_attribute}=") do |value| + super(value) unless options[:encrypted_attribute_only] + + send("#{encrypted_attribute}=", value) + end + end + + # The list of Vault attributes. + # + # @return [Hash] + def __vault_attributes + @vault_attributes ||= {} + end + + # Validate that Vault options are all a-okay! This method will raise + # exceptions if something does not make sense. + def _vault_validate_options!(options) + if options[:serializer] + if options[:encode] || options[:decode] + raise Vault::Rails::ValidationFailedError, "Cannot use a " \ + "custom encoder/decoder if a `:serializer' is specified!" + end + end + + if options[:encode] && !options[:decode] + raise Vault::Rails::ValidationFailedError, "Cannot specify " \ + "`:encode' without specifying `:decode' as well!" + end + + if options[:decode] && !options[:encode] + raise Vault::Rails::ValidationFailedError, "Cannot specify " \ + "`:decode' without specifying `:encode' as well!" + end + end + + def vault_lazy_decrypt? + !!@vault_lazy_decrypt + end + + def vault_lazy_decrypt! + @vault_lazy_decrypt = true + end + + # works only with convergent encryption + def vault_persist_all(attribute, records, plaintexts, validate: true) + options = __vault_attributes[attribute] + + Vault::PerformInBatches.new(attribute, options).encrypt(records, plaintexts, validate: validate) + end + + # works only with convergent encryption + # relevant only if lazy decryption is enabled + def vault_load_all(attribute, records) + options = __vault_attributes[attribute] + + Vault::PerformInBatches.new(attribute, options).decrypt(records) + end + + def encrypt_value(attribute, value) + options = __vault_attributes[attribute] + + key = options[:key] + path = options[:path] + serializer = options[:serializer] + convergent = options[:convergent] + + plaintext = serializer.encode(value) + + Vault::Rails.encrypt(path, key, plaintext, Vault.client, convergent) + end + + def encrypted_find_by(attributes) + find_by(search_options(attributes)) + end + + def encrypted_find_by!(attributes) + find_by!(search_options(attributes)) + end + + def encrypted_where(attributes) + where(search_options(attributes)) + end + + def encrypted_where_not(attributes) + where.not(search_options(attributes)) + end + + private + + def search_options(attributes) + {}.tap do |search_options| + attributes.each do |attribute_name, attribute_value| + attribute_options = __vault_attributes[attribute_name] + encrypted_column = attribute_options[:encrypted_column] + + unless attribute_options[:convergent] + raise ArgumentError, 'You cannot search with non-convergent fields' + end + + search_options[encrypted_column] = encrypt_value(attribute_name, attribute_value) + end + end + end + end + + included do + # After a resource has been initialized, immediately communicate with + # Vault and decrypt any attributes unless vault_lazy_decrypt is set. + after_initialize :__vault_load_attributes! + + # After we save the record, persist all the values to Vault and reload + # them attributes from Vault to ensure we have the proper attributes set. + # The reason we use `after_save` here is because a `before_save` could + # run too early in the callback process. If a user is changing Vault + # attributes in a callback, it is possible that our callback will run + # before theirs, resulting in attributes that are not persisted. + after_save :__vault_persist_attributes! + + # Decrypt all the attributes from Vault. + # @return [true] + def __vault_load_attributes! + return if self.class.vault_lazy_decrypt? + + self.class.__vault_attributes.each do |attribute, options| + self.__vault_load_attribute!(attribute, options) + end + end + + # Decrypt and load a single attribute from Vault. + def __vault_load_attribute!(attribute, options) + key = options[:key] + path = options[:path] + serializer = options[:serializer] + column = options[:encrypted_column] + convergent = options[:convergent] + + # Load the ciphertext + ciphertext = read_attribute(column) + + # If the user provided a value for the attribute, do not try to load it from Vault + return if instance_variable_get("@#{attribute}") + + # Load the plaintext value + plaintext = Vault::Rails.decrypt(path, key, ciphertext, Vault.client, convergent) + + # Deserialize the plaintext value, if a serializer exists + plaintext = serializer.decode(plaintext) if serializer + + # Write the virtual attribute with the plaintext value + instance_variable_set("@#{attribute}", plaintext) + end + + def cast_value_to_type(options, value) + type_constant_name = options.fetch(:type, :value).to_s.camelize + type = ActiveRecord::Type.const_get(type_constant_name).new + + if type.respond_to?(:type_cast_from_user) + type.type_cast_from_user(value) + else + value + end + end + + # Encrypt all the attributes using Vault and set the encrypted values back + # on this model. + # @return [true] + def __vault_persist_attributes! + changes = __vault_encrypt_attributes! + + # If there are any changes to the model, update them all at once, + # skipping any callbacks and validation. This is okay, because we are + # already in a transaction due to the callback. + self.update_columns(changes) if !changes.empty? + + true + end + + def __vault_encrypt_attributes! + changes = {} + + self.class.__vault_attributes.each do |attribute, options| + if c = self.__vault_encrypt_attribute!(attribute, options) + changes.merge!(c) + end + end + + changes + end + + # Encrypt a single attribute using Vault and persist back onto the + # encrypted attribute value. + def __vault_encrypt_attribute!(attribute, options) + # Only persist changed attributes to minimize requests - this helps + # minimize the number of requests to Vault. + return unless changed.include?("#{attribute}") + + column = options[:encrypted_column] + + # Get the current value of the plaintext attribute + plaintext = instance_variable_get("@#{attribute}") + + # Generate the ciphertext and store it back as an attribute + ciphertext = self.class.encrypt_value(attribute, plaintext) + + # Write the attribute back, so that we don't have to reload the record + # to get the ciphertext + write_attribute(column, ciphertext) + + # Return the updated column so we can save + { column => ciphertext } + end + + # Override the reload method to reload the Vault attributes. This will + # ensure that we always have the most recent data from Vault when we + # reload a record from the database. + def reload(*) + super.tap do + # Unset all the instance variables to force the new data to be pulled from Vault + self.class.__vault_attributes.each do |attribute, _| + if instance_variable_defined?("@#{attribute}") + self.remove_instance_variable("@#{attribute}") + end + end + + self.__vault_load_attributes! + end + end + end + end + end +end diff --git a/lib/vault/perform_in_batches.rb b/lib/vault/perform_in_batches.rb index 4244e4ec..14750b41 100644 --- a/lib/vault/perform_in_batches.rb +++ b/lib/vault/perform_in_batches.rb @@ -32,9 +32,13 @@ def decrypt(records) plaintexts = deserialize(raw_plaintexts) records.each_with_index do |record, index| - record.__vault_loaded_attributes << attribute + if Vault::Rails.latest? + record.__vault_loaded_attributes << attribute - record.write_attribute(attribute, plaintexts[index]) + record.write_attribute(attribute, plaintexts[index]) + else + record.instance_variable_set("@#{attribute}", plaintexts[index]) + end end end diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 07406c18..8453bb02 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -16,6 +16,7 @@ require_relative 'rails/serializers/time_serializer' require_relative 'rails/serializers/date_time_serializer' require_relative 'rails/serializers/ipaddr_serializer' +require_relative 'rails/serializers/string_serializer' require_relative 'rails/version' module Vault @@ -32,7 +33,8 @@ module Rails datetime: Vault::Rails::Serializers::DateTimeSerializer, ipaddr: Vault::Rails::Serializers::IPAddrSerializer, inet: Vault::Rails::Serializers::IPAddrSerializer, - cidr: Vault::Rails::Serializers::IPAddrSerializer + cidr: Vault::Rails::Serializers::IPAddrSerializer, + string: Vault::Rails::Serializers::StringSerializer }.freeze # The warning string to print when running in development mode. diff --git a/lib/vault/rails/serializers/string_serializer.rb b/lib/vault/rails/serializers/string_serializer.rb new file mode 100644 index 00000000..1c914ff8 --- /dev/null +++ b/lib/vault/rails/serializers/string_serializer.rb @@ -0,0 +1,17 @@ +module Vault + module Rails + module Serializers + module StringSerializer + module_function + + def encode(value) + value.blank? ? value : value.to_s + end + + def decode(value) + value + end + end + end + end +end diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 244c74de..91815123 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,5 +1,9 @@ module Vault module Rails VERSION = "1.0.1" + + def self.latest? + ActiveRecord.version >= Gem::Version.new('5.0.0') + end end end diff --git a/spec/dummy/app/models/lazy_person.rb b/spec/dummy/app/models/lazy_person.rb index 8ae1005e..ccdf25c2 100644 --- a/spec/dummy/app/models/lazy_person.rb +++ b/spec/dummy/app/models/lazy_person.rb @@ -9,6 +9,9 @@ class LazyPerson < ActiveRecord::Base vault_attribute :ssn + vault_attribute :date_of_birth_plaintext, type: :date + vault_attribute_proxy :date_of_birth, :date_of_birth_plaintext + vault_attribute :credit_card, encrypted_column: :cc_encrypted, path: "credit-secrets", diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index 25461dbb..ecfc720f 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -3,6 +3,9 @@ class Person < ActiveRecord::Base include Vault::EncryptedModel + vault_attribute :date_of_birth_plaintext, type: :date + vault_attribute_proxy :date_of_birth, :date_of_birth_plaintext + vault_attribute :county_plaintext, encrypted_column: :county_encrypted vault_attribute_proxy :county, :county_plaintext @@ -33,7 +36,7 @@ class Person < ActiveRecord::Base vault_attribute :driving_licence_number, convergent: true validates :driving_licence_number, vault_uniqueness: true, allow_nil: true - vault_attribute :ip_address, convergent: true, serialize: :ipaddr + vault_attribute :ip_address, convergent: true, serialize: :ipaddr, type: 'IPAddr' validates :ip_address, vault_uniqueness: true, allow_nil: true vault_attribute :integer_data, @@ -45,7 +48,7 @@ class Person < ActiveRecord::Base serialize: :float vault_attribute :time_data, - type: ActiveRecord::Type::Time.new, + type: :time, encode: -> (raw) { raw.to_s if raw }, decode: -> (raw) { raw.to_time if raw } end diff --git a/spec/dummy/db/migrate/20181030234312_add_date_of_birth_to_people.rb b/spec/dummy/db/migrate/20181030234312_add_date_of_birth_to_people.rb new file mode 100644 index 00000000..cd3ffa48 --- /dev/null +++ b/spec/dummy/db/migrate/20181030234312_add_date_of_birth_to_people.rb @@ -0,0 +1,6 @@ +class AddDateOfBirthToPeople < ActiveRecord::Migration[5.0] + def change + add_column :people, :date_of_birth, :string + add_column :people, :date_of_birth_encrypted, :string + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb index f9e531a7..6c6f8b06 100644 --- a/spec/dummy/db/schema.rb +++ b/spec/dummy/db/schema.rb @@ -30,6 +30,8 @@ t.string "county_encrypted" t.string "state" t.string "state_encrypted" + t.string "date_of_birth" + t.string "date_of_birth_encrypted" t.string "passport_number_encrypted" t.string "driving_licence_number_encrypted" t.string "ip_address_encrypted" diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index b3f9e162..4131f25d 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -153,7 +153,9 @@ person = LazyPerson.create!(ssn: "123-45-6789", non_ascii: 'some text') expect(person.ssn).not_to be_nil - expect(person).not_to receive(:__vault_load_attributes!) + if Vault::Rails.latest? + expect(person).not_to receive(:__vault_load_attributes!) + end person.reload expect(person.read_attribute(:ssn)).to be nil @@ -216,7 +218,9 @@ person.ssn = "111-11-1111" - expect(person).to receive(:__vault_initialize_attributes!).once.and_call_original + if Vault::Rails.latest? + expect(person).to receive(:__vault_initialize_attributes!).once.and_call_original + end expect(person).to receive(:__vault_load_attribute!).once.with(:ssn, any_args).and_call_original person.reload @@ -441,6 +445,18 @@ end end + context 'when called with type other than string' do + it 'casts the value to the correct type' do + person = Person.new + date_string = '2000-10-10' + date = Date.parse(date_string) + + person.date_of_birth_plaintext = date_string + + expect(person.date_of_birth_plaintext).to eq date + end + end + context 'with errors' do it 'raises the appropriate exception' do expect { diff --git a/spec/unit/attribute_proxy_spec.rb b/spec/unit/attribute_proxy_spec.rb index c3cd11d4..1b214d3f 100644 --- a/spec/unit/attribute_proxy_spec.rb +++ b/spec/unit/attribute_proxy_spec.rb @@ -3,6 +3,18 @@ RSpec.describe 'vault_attribute_proxy' do let(:person) { Person.new } + context 'when called with type other than string' do + it 'casts the value to the correct type' do + date_string = '2000-10-10' + date = Date.parse(date_string) + + person.date_of_birth = date_string + + expect(person.date_of_birth).to eq date + expect(person.date_of_birth_plaintext).to eq date + end + end + context 'with encrypted_attribute_only false' do it 'fills both attributes' do county = 'Orange' diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 283373d4..dc5eb723 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -81,38 +81,40 @@ expect(person_time.sec).to eq time.sec end - it 'raises an error with unknown attribute type' do - expect do - Person.vault_attribute :unrecognized_attr, type: :unrecognized - end.to raise_error RuntimeError, /Unrecognized attribute type/ - end + if Vault::Rails.latest? + it 'raises an error with unknown attribute type' do + expect do + Person.vault_attribute :unrecognized_attr, type: :unrecognized + end.to raise_error RuntimeError, /Unrecognized attribute type/ + end - it 'defines a default serialzer if it has one for the type' do - time_data_vault_options = TypedPerson.__vault_attributes[:time_data] - expect(time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::TimeSerializer + it 'defines a default serialzer if it has one for the type' do + time_data_vault_options = TypedPerson.__vault_attributes[:time_data] + expect(time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::TimeSerializer - integer_data_vault_options = TypedPerson.__vault_attributes[:integer_data] - expect(integer_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::IntegerSerializer + integer_data_vault_options = TypedPerson.__vault_attributes[:integer_data] + expect(integer_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::IntegerSerializer - float_data_vault_options = TypedPerson.__vault_attributes[:float_data] - expect(float_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::FloatSerializer + float_data_vault_options = TypedPerson.__vault_attributes[:float_data] + expect(float_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::FloatSerializer - date_data_vault_options = TypedPerson.__vault_attributes[:date_data] - expect(date_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateSerializer + date_data_vault_options = TypedPerson.__vault_attributes[:date_data] + expect(date_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateSerializer - date_time_data_vault_options = TypedPerson.__vault_attributes[:date_time_data] - expect(date_time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateTimeSerializer - end + date_time_data_vault_options = TypedPerson.__vault_attributes[:date_time_data] + expect(date_time_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::DateTimeSerializer + end - it 'does not add a default serialzer if it does not have one for the type' do - string_data_vault_options = TypedPerson.__vault_attributes[:string_data] - expect(string_data_vault_options[:serializer]).to be_nil + it 'does not add a default serialzer if it does not have one for the type' do + string_data_vault_options = TypedPerson.__vault_attributes[:string_data] + expect(string_data_vault_options[:serializer]).to eq Vault::Rails::Serializers::StringSerializer - decimal_data_vault_options = TypedPerson.__vault_attributes[:decimal_data] - expect(decimal_data_vault_options[:serializer]).to be_nil + decimal_data_vault_options = TypedPerson.__vault_attributes[:decimal_data] + expect(decimal_data_vault_options[:serializer]).to be_nil - text_data_vault_options = TypedPerson.__vault_attributes[:text_data] - expect(text_data_vault_options[:serializer]).to be_nil + text_data_vault_options = TypedPerson.__vault_attributes[:text_data] + expect(text_data_vault_options[:serializer]).to be_nil + end end it 'allows overriding the default serialzer via the `serializer` option' do diff --git a/spec/unit/perform_in_batches_spec.rb b/spec/unit/perform_in_batches_spec.rb index bbdd0b56..2f133a55 100644 --- a/spec/unit/perform_in_batches_spec.rb +++ b/spec/unit/perform_in_batches_spec.rb @@ -154,18 +154,27 @@ .with('test_path', 'test_key', %w(ciphertext1 ciphertext2), Vault.client) .and_return(%w(plaintext1 plaintext2)) - first_record_loaded_attributes = [] - allow(first_record).to receive('__vault_loaded_attributes').and_return(first_record_loaded_attributes) - second_record_loaded_attributes = [] - allow(second_record).to receive('__vault_loaded_attributes').and_return(second_record_loaded_attributes) + if Vault::Rails.latest? + first_record_loaded_attributes = [] + allow(first_record).to receive('__vault_loaded_attributes').and_return(first_record_loaded_attributes) + second_record_loaded_attributes = [] + allow(second_record).to receive('__vault_loaded_attributes').and_return(second_record_loaded_attributes) + end - expect(first_record).to receive('write_attribute').with('test_attribute', 'plaintext1') - expect(second_record).to receive('write_attribute').with('test_attribute', 'plaintext2') + if Vault::Rails.latest? + expect(first_record).to receive('write_attribute').with('test_attribute', 'plaintext1') + expect(second_record).to receive('write_attribute').with('test_attribute', 'plaintext2') + else + expect(first_record).to receive('instance_variable_set').with('@test_attribute', 'plaintext1') + expect(second_record).to receive('instance_variable_set').with('@test_attribute', 'plaintext2') + end Vault::PerformInBatches.new(attribute, options).decrypt(records) - expect(first_record_loaded_attributes).to include(attribute) - expect(second_record_loaded_attributes).to include(attribute) + if Vault::Rails.latest? + expect(first_record_loaded_attributes).to include(attribute) + expect(second_record_loaded_attributes).to include(attribute) + end end context 'with given serializer' do @@ -190,18 +199,27 @@ .with('test_path', 'test_key', %w(ciphertext1 ciphertext2), Vault.client) .and_return(%w(100 200)) - first_record_loaded_attributes = [] - allow(first_record).to receive('__vault_loaded_attributes').and_return(first_record_loaded_attributes) - second_record_loaded_attributes = [] - allow(second_record).to receive('__vault_loaded_attributes').and_return(second_record_loaded_attributes) - - expect(first_record).to receive('write_attribute').with('test_attribute', 100) - expect(second_record).to receive('write_attribute').with('test_attribute', 200) + if Vault::Rails.latest? + first_record_loaded_attributes = [] + allow(first_record).to receive('__vault_loaded_attributes').and_return(first_record_loaded_attributes) + second_record_loaded_attributes = [] + allow(second_record).to receive('__vault_loaded_attributes').and_return(second_record_loaded_attributes) + end + + if Vault::Rails.latest? + expect(first_record).to receive('write_attribute').with('test_attribute', 100) + expect(second_record).to receive('write_attribute').with('test_attribute', 200) + else + expect(first_record).to receive('instance_variable_set').with('@test_attribute', 100) + expect(second_record).to receive('instance_variable_set').with('@test_attribute', 200) + end Vault::PerformInBatches.new(attribute, options).decrypt(records) - expect(first_record_loaded_attributes).to include(attribute) - expect(second_record_loaded_attributes).to include(attribute) + if Vault::Rails.latest? + expect(first_record_loaded_attributes).to include(attribute) + expect(second_record_loaded_attributes).to include(attribute) + end end end end diff --git a/spec/unit/rails/serializers/string_serializer_spec.rb b/spec/unit/rails/serializers/string_serializer_spec.rb new file mode 100644 index 00000000..f4440649 --- /dev/null +++ b/spec/unit/rails/serializers/string_serializer_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Vault::Rails::Serializers::StringSerializer do + context 'blank values' do + it 'encodes blank values without changing them' do + expect(subject.encode(nil)).to eq nil + end + end + + it 'encodes values to strings' do + expect(subject.encode({a: 3})).to eq "{:a=>3}" + end + + it 'decodes the value by simply returing it' do + expect(subject.decode('foo')).to eq 'foo' + end +end From 0a0bc923a617615cb2329116942585ce51fd4324 Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Wed, 17 Apr 2019 13:22:01 +0100 Subject: [PATCH 081/143] Move version requiring before all --- lib/vault/rails.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 8453bb02..455a4710 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -3,6 +3,7 @@ require 'base64' require 'json' +require_relative 'rails/version' require_relative 'encrypted_model' require_relative 'attribute_proxy' require_relative 'perform_in_batches' @@ -17,7 +18,6 @@ require_relative 'rails/serializers/date_time_serializer' require_relative 'rails/serializers/ipaddr_serializer' require_relative 'rails/serializers/string_serializer' -require_relative 'rails/version' module Vault module Rails From 2a4107baa7d92fa8164959422b1aaea46a183951 Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Wed, 17 Apr 2019 15:42:20 +0100 Subject: [PATCH 082/143] Bump version to 2.0.0 --- CHANGELOG.md | 11 +++++++++++ gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5558664c..00d30c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,15 @@ # Vault Rails Changelog +## 2.0.0 (April 17, 2019) + +NEW FEATURES +- Added support for Rails 4.2.x + +IMPROVEMENTS +- No longer required to include the module `Vault::AttributeProxy` + +BREAKING CHANGES +- You can not pass an `ActiveRecord::Type` through the `type` option on `vault_attribute`, to do this just specify the type as a symbol. + ## 1.0.1 (March 14, 2019) NEW FEATURES diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 77ba481c..5e3f75b0 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.1) + fc-vault-rails (2.0.0) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index e71467a0..0d9a7cb8 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.1) + fc-vault-rails (2.0.0) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 736e10eb..340baa4e 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.1) + fc-vault-rails (2.0.0) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 47de987d..fa02f929 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (1.0.1) + fc-vault-rails (2.0.0) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 91815123..57768064 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "1.0.1" + VERSION = "2.0.0" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From 9fe3af940218068ad4ebeda5040719ad4a98777d Mon Sep 17 00:00:00 2001 From: Ahmet Date: Mon, 29 Apr 2019 11:56:04 +0100 Subject: [PATCH 083/143] Override .attributes method on legacy EncryptedModel and adds .unencrypted_attributes method --- lib/vault/latest/encrypted_model.rb | 4 ++ lib/vault/legacy/encrypted_model.rb | 13 +++++ spec/unit/encrypted_model_spec.rb | 78 +++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb index 4a82e239..0d91cc87 100644 --- a/lib/vault/latest/encrypted_model.rb +++ b/lib/vault/latest/encrypted_model.rb @@ -390,6 +390,10 @@ def __vault_encrypt_attribute!(attribute, options, in_after_save: false) { column => ciphertext } end + def unencrypted_attributes + attributes.delete_if { |attr| attr.ends_with?('_encrypted') } + end + # Override the reload method to reload the Vault attributes. This will # ensure that we always have the most recent data from Vault when we # reload a record from the database. diff --git a/lib/vault/legacy/encrypted_model.rb b/lib/vault/legacy/encrypted_model.rb index ea40060d..34b8e0e2 100644 --- a/lib/vault/legacy/encrypted_model.rb +++ b/lib/vault/legacy/encrypted_model.rb @@ -286,6 +286,15 @@ def __vault_load_attributes! end end + def attributes + super.tap do |attrs| + missing_keys = self.class.__vault_attributes.keys.map(&:to_s) - attrs.keys + missing_keys.each do |key| + attrs.store(key, public_send(key)) + end + end + end + # Decrypt and load a single attribute from Vault. def __vault_load_attribute!(attribute, options) key = options[:key] @@ -370,6 +379,10 @@ def __vault_encrypt_attribute!(attribute, options) { column => ciphertext } end + def unencrypted_attributes + attributes.delete_if { |attr| attr.ends_with?('_encrypted') } + end + # Override the reload method to reload the Vault attributes. This will # ensure that we always have the most recent data from Vault when we # reload a record from the database. diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index dc5eb723..853f2b84 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -134,6 +134,84 @@ end end + describe '#attributes' do + let(:person) { Person.new } + + it 'returns all attributes' do + expect(person.attributes).to eq( + "business_card" => nil, + "business_card_encrypted" => nil, + "cc_encrypted" => nil, + "county" => nil, + "county_encrypted" => nil, + "county_plaintext" => nil, + "created_at" => nil, + "credit_card" => nil, + "date_of_birth" => nil, + "date_of_birth_encrypted" => nil, + "date_of_birth_plaintext" => nil, + "details" => nil, + "details_encrypted" => nil, + "driving_licence_number" => nil, + "driving_licence_number_encrypted" => nil, + "email" => nil, + "email_encrypted" => nil, + "favorite_color" => nil, + "favorite_color_encrypted" => nil, + "float_data" => nil, + "float_data_encrypted" => nil, + "id" => nil, + "integer_data" => nil, + "integer_data_encrypted" => nil, + "ip_address" => nil, + "ip_address_encrypted" => nil, + "name" => nil, + "non_ascii" => nil, + "non_ascii_encrypted" => nil, + "passport_number_encrypted" => nil, + "ssn" => nil, + "ssn_encrypted" => nil, + "state" => nil, + "state_encrypted" => nil, + "state_plaintext" => nil, + "time_data" => nil, + "time_data_encrypted" => nil, + "updated_at" => nil + ) + end + end + + describe '#unencrypted_attributes' do + let(:person) { Person.new } + + it 'returns all attributes apart from encrypted fields' do + expect(person.unencrypted_attributes).to eq( + 'business_card' => nil, + 'county' => nil, + 'county_plaintext' => nil, + 'created_at' => nil, + 'credit_card' => nil, + 'date_of_birth' => nil, + 'date_of_birth_plaintext' => nil, + 'details' => nil, + 'driving_licence_number' => nil, + 'email' => nil, + 'favorite_color' => nil, + 'float_data' => nil, + 'id' => nil, + 'integer_data' => nil, + 'ip_address' => nil, + 'name' => nil, + 'non_ascii' => nil, + 'ssn' => nil, + 'state' => nil, + 'state_plaintext' => nil, + 'time_data' => nil, + 'updated_at' => nil + ) + end + end + describe '#vault_persist_before_save!' do context "when not used" do # Person hasn't had `vault_persist_before_save!` called on it From 2263c4b41d3a80e8af2eb83c692ffd976037e467 Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Tue, 30 Apr 2019 09:37:38 +0100 Subject: [PATCH 084/143] Update unencrypted_attributes method to use vault_attributes for retrieving encryted_column --- lib/vault/latest/encrypted_model.rb | 3 ++- lib/vault/legacy/encrypted_model.rb | 3 ++- spec/dummy/app/models/person.rb | 4 +++- spec/unit/encrypted_model_spec.rb | 2 ++ 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb index 0d91cc87..8e884543 100644 --- a/lib/vault/latest/encrypted_model.rb +++ b/lib/vault/latest/encrypted_model.rb @@ -391,7 +391,8 @@ def __vault_encrypt_attribute!(attribute, options, in_after_save: false) end def unencrypted_attributes - attributes.delete_if { |attr| attr.ends_with?('_encrypted') } + encrypted_attributes = self.class.__vault_attributes.values.map {|x| x[:encrypted_column].to_s } + attributes.delete_if { |attribute| encrypted_attributes.include?(attribute) } end # Override the reload method to reload the Vault attributes. This will diff --git a/lib/vault/legacy/encrypted_model.rb b/lib/vault/legacy/encrypted_model.rb index 34b8e0e2..05b579f5 100644 --- a/lib/vault/legacy/encrypted_model.rb +++ b/lib/vault/legacy/encrypted_model.rb @@ -380,7 +380,8 @@ def __vault_encrypt_attribute!(attribute, options) end def unencrypted_attributes - attributes.delete_if { |attr| attr.ends_with?('_encrypted') } + encrypted_attributes = self.class.__vault_attributes.values.map {|x| x[:encrypted_column].to_s } + attributes.delete_if { |attribute| encrypted_attributes.include?(attribute) } end # Override the reload method to reload the Vault attributes. This will diff --git a/spec/dummy/app/models/person.rb b/spec/dummy/app/models/person.rb index ecfc720f..cfdc9567 100644 --- a/spec/dummy/app/models/person.rb +++ b/spec/dummy/app/models/person.rb @@ -3,9 +3,11 @@ class Person < ActiveRecord::Base include Vault::EncryptedModel - vault_attribute :date_of_birth_plaintext, type: :date + vault_attribute :date_of_birth_plaintext, type: :date, encrypted_column: :date_of_birth_encrypted vault_attribute_proxy :date_of_birth, :date_of_birth_plaintext + vault_attribute :passport_number, encrypted_column: :passport_number_encrypted + vault_attribute :county_plaintext, encrypted_column: :county_encrypted vault_attribute_proxy :county, :county_plaintext diff --git a/spec/unit/encrypted_model_spec.rb b/spec/unit/encrypted_model_spec.rb index 853f2b84..65734fe4 100644 --- a/spec/unit/encrypted_model_spec.rb +++ b/spec/unit/encrypted_model_spec.rb @@ -168,6 +168,7 @@ "name" => nil, "non_ascii" => nil, "non_ascii_encrypted" => nil, + 'passport_number' => nil, "passport_number_encrypted" => nil, "ssn" => nil, "ssn_encrypted" => nil, @@ -203,6 +204,7 @@ 'ip_address' => nil, 'name' => nil, 'non_ascii' => nil, + 'passport_number' => nil, 'ssn' => nil, 'state' => nil, 'state_plaintext' => nil, From 2923a6969c6855bd1506e4525cd9ff0a14e06ca8 Mon Sep 17 00:00:00 2001 From: Ahmet Date: Thu, 2 May 2019 14:04:54 +0100 Subject: [PATCH 085/143] Bump version to 2.0.1 --- CHANGELOG.md | 10 +++++++++- gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 6 files changed, 14 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00d30c59..e4cbf3ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Vault Rails Changelog -## 2.0.0 (April 17, 2019) +## 2.0.1 (May 2, 2019) + +NEW FEATURES +- Added `.unencrypted_attributes` which returns all attributes ignoring the `encrypted_column` + +IMPROVEMENTS +- Fixes issue with `.attributes` on rails >= 4.2 and < 5 now returning the `vault_attribute` correctly. + +## 2.0.0 (April 17, 2019) NEW FEATURES - Added support for Rails 4.2.x diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 5e3f75b0..36cecc65 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.0) + fc-vault-rails (2.0.1) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 0d9a7cb8..3e1ada71 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.0) + fc-vault-rails (2.0.1) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 340baa4e..eb26b0e6 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.0) + fc-vault-rails (2.0.1) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index fa02f929..8a57ada8 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.0) + fc-vault-rails (2.0.1) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 57768064..5b29888a 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.0.0" + VERSION = "2.0.1" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From 2d40b4e2dd06b503cf3b2440821cfd20238ab2eb Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Thu, 16 May 2019 12:25:21 +0100 Subject: [PATCH 086/143] Fixes bug with memory decryption not handing blank values --- lib/vault/rails.rb | 2 +- spec/integration/rails_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/vault/rails.rb b/lib/vault/rails.rb index 455a4710..e56af840 100644 --- a/lib/vault/rails.rb +++ b/lib/vault/rails.rb @@ -220,7 +220,7 @@ def memory_batch_encrypt(path, key, plaintexts, _client) def memory_decrypt(path, key, ciphertext, _client, convergent) log_warning(DEV_WARNING) if self.in_memory_warnings_enabled? - return nil if ciphertext.nil? + return ciphertext if ciphertext.blank? cipher = OpenSSL::Cipher::AES.new(128, :CBC) cipher.decrypt diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 4131f25d..9ad8c8a0 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -518,6 +518,20 @@ expect(first_person.email_encrypted).not_to eq(second_person.email_encrypted) end end + + context '.vault_load_all' do + it 'works with records with nil and blank values' do + first_person = LazyPerson.create!(passport_number: nil) + second_person = LazyPerson.create!(passport_number: '') + + first_person.reload + second_person.reload + + LazyPerson.vault_load_all(:passport_number, [first_person, second_person]) + expect(first_person.passport_number).to eq(nil) + expect(second_person.passport_number).to eq('') + end + end end context 'uniqueness validation' do From 0c826c71aea0e3ece0c4fe30f4f97fa7e00db107 Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Thu, 16 May 2019 14:21:26 +0100 Subject: [PATCH 087/143] Version 2.0.2 --- CHANGELOG.md | 5 +++++ gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4cbf3ed..825387e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Vault Rails Changelog +## 2.0.2 (May 16, 2019) + +IMPROVEMENTS +- Fixes issue when a blank string ciphertext is used by the `memory_decrypt` method. + ## 2.0.1 (May 2, 2019) NEW FEATURES diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 36cecc65..d019413c 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.1) + fc-vault-rails (2.0.2) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 3e1ada71..5fd31599 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.1) + fc-vault-rails (2.0.2) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index eb26b0e6..98f15a57 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.1) + fc-vault-rails (2.0.2) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 8a57ada8..48c06df7 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.1) + fc-vault-rails (2.0.2) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 5b29888a..f0652e79 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.0.1" + VERSION = "2.0.2" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From 94da2ba8e23c268df72f99c8ab55280529eb5539 Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Thu, 22 Aug 2019 10:25:35 +0100 Subject: [PATCH 088/143] Update JSONSerializer when encoding to return value if already a JSON string --- lib/vault/rails/serializers/json_serializer.rb | 2 ++ .../rails/serializers/json_serializer_spec.rb | 16 ++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/vault/rails/serializers/json_serializer.rb b/lib/vault/rails/serializers/json_serializer.rb index aabd57b9..7171ca79 100644 --- a/lib/vault/rails/serializers/json_serializer.rb +++ b/lib/vault/rails/serializers/json_serializer.rb @@ -11,9 +11,11 @@ module JSONSerializer def self.encode(raw) return if raw.nil? + return raw if raw.is_a?(String) JSON.fast_generate(raw) end + def self.decode(raw) return if raw.nil? JSON.parse(raw, DECODE_OPTIONS) diff --git a/spec/unit/rails/serializers/json_serializer_spec.rb b/spec/unit/rails/serializers/json_serializer_spec.rb index fc7f6da8..0cbe3afc 100644 --- a/spec/unit/rails/serializers/json_serializer_spec.rb +++ b/spec/unit/rails/serializers/json_serializer_spec.rb @@ -1,11 +1,19 @@ require 'spec_helper' describe Vault::Rails::Serializers::JSONSerializer do - it 'encodes values to strings' do - expect(subject.encode({"foo" => "bar", "baz" => 1})).to eq '{"foo":"bar","baz":1}' + context '.encode' do + it 'encodes values to strings' do + expect(subject.encode({"foo" => "bar", "baz" => 1})).to eq '{"foo":"bar","baz":1}' + end + + it 'returns values already encoded as a JSON string' do + expect(subject.encode('{"anonymised":true}')).to eq('{"anonymised":true}') + end end - it 'decodes values from strings' do - expect(subject.decode('{"foo":"bar","baz":1}')).to eq({"foo" => "bar", "baz" => 1}) + context '.decode' do + it 'decodes values from strings' do + expect(subject.decode('{"foo":"bar","baz":1}')).to eq({"foo" => "bar", "baz" => 1}) + end end end From 191b1ee5346a1220dc0cc97e03de1dc3bd4b2957 Mon Sep 17 00:00:00 2001 From: Ahmet Abdi Date: Thu, 22 Aug 2019 13:08:18 +0100 Subject: [PATCH 089/143] Version 2.0.3 --- CHANGELOG.md | 5 +++++ gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 825387e7..80622eae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,9 @@ # Vault Rails Changelog +## 2.0.3 (August 22, 2019) + +BUG FIXES +- Fix bug where JSONSerializer would raise an error when passed a string + ## 2.0.2 (May 16, 2019) IMPROVEMENTS diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index d019413c..cdd289d3 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.2) + fc-vault-rails (2.0.3) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 5fd31599..8015f982 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.2) + fc-vault-rails (2.0.3) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index 98f15a57..c5438ecf 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.2) + fc-vault-rails (2.0.3) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 48c06df7..50e7eecf 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.2) + fc-vault-rails (2.0.3) activerecord (>= 4.2, < 6.0) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index f0652e79..6a7163cb 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.0.2" + VERSION = "2.0.3" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From 8fcc0375768e8af0ef7f4884ea4ee8979be6dba0 Mon Sep 17 00:00:00 2001 From: Kleber Correia Date: Sun, 1 Dec 2019 20:13:10 +0000 Subject: [PATCH 090/143] Remove travis-ci dependency by moving completely to CircleCI The "wwtd" rubygem along with Travis CI configuration file basically run the suite test against different versions of Ruby and Rails. This causes problems when the ruby/rails combination are ruby versions lower than 2.5 and the rails version is 6 since this version of rails only supports ruby version 2.5.0 or higher. --- .circleci/config.yml | 154 +++++++++++++++++++++++++++++++---------- .travis.yml | 32 --------- fc-vault-rails.gemspec | 1 - 3 files changed, 116 insertions(+), 71 deletions(-) delete mode 100644 .travis.yml diff --git a/.circleci/config.yml b/.circleci/config.yml index 51f39e38..64441002 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,44 +10,106 @@ defaults: &defaults RACK_ENV: test VAULT_VERSION: 0.10.4 -jobs: - tests: - <<: *defaults +aliases: + environment: &environment + RACK_ENV: test + VAULT_VERSION: 0.10.4 + command: &ruby-command | + bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 + bundle exec rake app:db:create + bundle exec rake app:db:schema:load + bundle exec rake app:db:test:prepare + bundle exec rake + cache: + - restore_cache: &restore_cache + keys: + - vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum "Appraisals" }} + - save_cache: &save_cache + key: vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum "Appraisals" }} + paths: + - vendor/bundle + runs: + - run: &install-vault + name: Install Vault + command: | + wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + unzip vault.zip + sudo mv vault /usr/local/bin/ + - run: &rails4 + shell: /bin/bash -l + name: Rails 4.2 + environment: + BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile + command: *ruby-command + - run: &rails5 + shell: /bin/bash -l + name: Rails 5.0 + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile + command: *ruby-command + - run: &rails51 + shell: /bin/bash -l + name: Rails 5.1 + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile + command: *ruby-command + - run: &rails52 + shell: /bin/bash -l + name: Rails 5.2 + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile + command: *ruby-command + - run: &rails6 + shell: /bin/bash -l + name: Rails 6 + environment: + BUNDLE_GEMFILE: gemfiles/rails_6.gemfile + command: *ruby-command + +jobs: + test_ruby_2_3: + docker: + - image: circleci/ruby:2.3 + environment: + RACK_ENV: test + VAULT_VERSION: 0.10.4 steps: - checkout - - run: yum -y install wget unzip sqlite - - run: yum clean all - - - run: - name: Install Vault - command: | - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip - unzip vault.zip - mv vault /usr/local/bin/ - - - restore_cache: - keys: - - vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum ".travis.yml" }} - - - run: - shell: /bin/bash -l - command: | - rvm use --default 2.5.1 - bundle check --path=/usr/local/rvm/gems || bundle install --path=/usr/local/rvm/gems - - - run: - shell: /bin/bash -l - command: | - bundle exec rake app:db:create - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec wwtd - - - save_cache: - key: vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum ".travis.yml" }} - paths: - - /usr/local/rvm/gems + - run: *install-vault + - run: *rails4 + - run: *rails5 + - run: *rails51 + - run: *rails52 + + test_ruby_2_4: + docker: + - image: circleci/ruby:2.4 + environment: + RACK_ENV: test + VAULT_VERSION: 0.10.4 + steps: + - checkout + - run: *install-vault + - run: *rails4 + - run: *rails5 + - run: *rails51 + - run: *rails52 + + test_ruby_2_5: + docker: + - image: circleci/ruby:2.5 + environment: + RACK_ENV: test + VAULT_VERSION: 0.10.4 + steps: + - checkout + - run: *install-vault + - run: *rails4 + - run: *rails5 + - run: *rails51 + - run: *rails52 + - run: *rails6 publish-pre-release: <<: *defaults @@ -128,7 +190,19 @@ workflows: test-and-release: jobs: - - tests: + - test_ruby_2_3: + context: org-global + filters: + tags: + only: /.*/ + + - test_ruby_2_4: + context: org-global + filters: + tags: + only: /.*/ + + - test_ruby_2_5: context: org-global filters: tags: @@ -142,7 +216,9 @@ workflows: tags: ignore: /.*/ requires: - - tests + - test_ruby_2_3 + - test_ruby_2_4 + - test_ruby_2_5 - publish-release: context: org-global @@ -152,4 +228,6 @@ workflows: tags: only: /^v[0-9]+(\.[0-9]+)*$/ requires: - - tests + - test_ruby_2_3 + - test_ruby_2_4 + - test_ruby_2_5 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7c5d3399..00000000 --- a/.travis.yml +++ /dev/null @@ -1,32 +0,0 @@ -language: ruby -cache: bundler -dist: trusty -sudo: false - -gemfile: - - Gemfile - - gemfiles/rails_4.2.gemfile - - gemfiles/rails_5.0.gemfile - - gemfiles/rails_5.1.gemfile - - gemfiles/rails_5.2.gemfile - -before_install: - - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip - - unzip vault.zip - - mkdir -p ~/bin - - mv vault ~/bin - - export PATH="~/bin:$PATH" - -branches: - only: - - master - -rvm: - - 2.3.7 - - 2.4.4 - - 2.5.1 - -before_script: - - bundle exec rake app:db:create - - bundle exec rake app:db:schema:load - - bundle exec rake app:db:test:prepare diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 9659e139..04aa31e7 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -28,5 +28,4 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec", "~> 3.2" s.add_development_dependency "sqlite3", "~> 1.3.6" - s.add_development_dependency "wwtd" end From c075fea87ed37656ed8049cf9d8e935175341485 Mon Sep 17 00:00:00 2001 From: Kleber Correia Date: Sun, 1 Dec 2019 20:25:24 +0000 Subject: [PATCH 091/143] Add Rails 6 support SQLite3 gem on ActiveRecord 6 needs to be higher than 1.3, this commit makes sure each Gemfile has its proper version --- Appraisals | 8 ++ fc-vault-rails.gemspec | 8 +- gemfiles/rails_4.2.gemfile | 1 + gemfiles/rails_4.2.gemfile.lock | 14 ++- gemfiles/rails_5.0.gemfile | 1 + gemfiles/rails_5.0.gemfile.lock | 14 ++- gemfiles/rails_5.1.gemfile | 1 + gemfiles/rails_5.1.gemfile.lock | 14 ++- gemfiles/rails_5.2.gemfile | 1 + gemfiles/rails_5.2.gemfile.lock | 14 ++- gemfiles/rails_6.gemfile | 8 ++ gemfiles/rails_6.gemfile.lock | 179 ++++++++++++++++++++++++++++++++ 12 files changed, 227 insertions(+), 36 deletions(-) create mode 100644 gemfiles/rails_6.gemfile create mode 100644 gemfiles/rails_6.gemfile.lock diff --git a/Appraisals b/Appraisals index e511c9ce..6b25703f 100644 --- a/Appraisals +++ b/Appraisals @@ -1,12 +1,20 @@ appraise "rails-4.2" do gem "rails", "~> 4.2.0" + gem 'sqlite3', '~> 1.3.13' end appraise "rails-5.0" do gem "rails", "~> 5.0.0" + gem 'sqlite3', '~> 1.3.13' end appraise "rails-5.1" do gem "rails", "~> 5.1.0" + gem 'sqlite3', '~> 1.3.13' end appraise "rails-5.2" do gem "rails", "~> 5.2.0" + gem 'sqlite3', '~> 1.3.13' +end +appraise "rails-6" do + gem "rails", "~> 6" + gem 'sqlite3', '~> 1.4' end diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 04aa31e7..95cca792 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,15 +17,15 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", [">= 4.2", "< 6.0"] + s.add_dependency "activerecord", ">= 4.2" s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", [">= 4.2", "< 6.0"] + s.add_development_dependency "rails", ">= 4.2" s.add_development_dependency "byebug" s.add_development_dependency "pry" - s.add_development_dependency "rake", "~> 10.0" + s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3.2" - s.add_development_dependency "sqlite3", "~> 1.3.6" + s.add_development_dependency "sqlite3", '~> 1.3' end diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 6977eb02..07b883b7 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" +gem "sqlite3", "~> 1.3.13" gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index cdd289d3..58361d76 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (2.0.3) - activerecord (>= 4.2, < 6.0) + activerecord (>= 4.2) vault (~> 0.7) GEM @@ -47,7 +47,7 @@ GEM rake thor (>= 0.14.0) arel (6.0.4) - aws-eventstream (1.0.2) + aws-eventstream (1.0.3) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) @@ -128,9 +128,8 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - vault (0.12.0) + vault (0.13.0) aws-sigv4 - wwtd (1.3.0) PLATFORMS ruby @@ -142,10 +141,9 @@ DEPENDENCIES fc-vault-rails! pry rails (~> 4.2.0) - rake (~> 10.0) + rake rspec (~> 3.2) - sqlite3 (~> 1.3.6) - wwtd + sqlite3 (~> 1.3.13) BUNDLED WITH - 1.17.2 + 1.17.3 diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile index 10f52e7a..23d47f65 100644 --- a/gemfiles/rails_5.0.gemfile +++ b/gemfiles/rails_5.0.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 5.0.0" +gem "sqlite3", "~> 1.3.13" gemspec path: "../" diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 8015f982..83a3dff5 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (2.0.3) - activerecord (>= 4.2, < 6.0) + activerecord (>= 4.2) vault (~> 0.7) GEM @@ -50,7 +50,7 @@ GEM rake thor (>= 0.14.0) arel (7.1.4) - aws-eventstream (1.0.2) + aws-eventstream (1.0.3) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) @@ -131,12 +131,11 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - vault (0.12.0) + vault (0.13.0) aws-sigv4 websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) - wwtd (1.3.0) PLATFORMS ruby @@ -148,10 +147,9 @@ DEPENDENCIES fc-vault-rails! pry rails (~> 5.0.0) - rake (~> 10.0) + rake rspec (~> 3.2) - sqlite3 (~> 1.3.6) - wwtd + sqlite3 (~> 1.3.13) BUNDLED WITH - 1.16.6 + 1.17.3 diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile index 6100e830..226e6174 100644 --- a/gemfiles/rails_5.1.gemfile +++ b/gemfiles/rails_5.1.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 5.1.0" +gem "sqlite3", "~> 1.3.13" gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index c5438ecf..dfa6b316 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (2.0.3) - activerecord (>= 4.2, < 6.0) + activerecord (>= 4.2) vault (~> 0.7) GEM @@ -50,7 +50,7 @@ GEM rake thor (>= 0.14.0) arel (8.0.0) - aws-eventstream (1.0.2) + aws-eventstream (1.0.3) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) @@ -131,12 +131,11 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - vault (0.12.0) + vault (0.13.0) aws-sigv4 websocket-driver (0.6.5) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) - wwtd (1.3.0) PLATFORMS ruby @@ -148,10 +147,9 @@ DEPENDENCIES fc-vault-rails! pry rails (~> 5.1.0) - rake (~> 10.0) + rake rspec (~> 3.2) - sqlite3 (~> 1.3.6) - wwtd + sqlite3 (~> 1.3.13) BUNDLED WITH - 1.16.6 + 1.17.3 diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile index 5a706dcb..9c104738 100644 --- a/gemfiles/rails_5.2.gemfile +++ b/gemfiles/rails_5.2.gemfile @@ -3,5 +3,6 @@ source "https://rubygems.org" gem "rails", "~> 5.2.0" +gem "sqlite3", "~> 1.3.13" gemspec path: "../" diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 50e7eecf..bc96849f 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -2,7 +2,7 @@ PATH remote: .. specs: fc-vault-rails (2.0.3) - activerecord (>= 4.2, < 6.0) + activerecord (>= 4.2) vault (~> 0.7) GEM @@ -54,7 +54,7 @@ GEM rake thor (>= 0.14.0) arel (9.0.0) - aws-eventstream (1.0.2) + aws-eventstream (1.0.3) aws-sigv4 (1.1.0) aws-eventstream (~> 1.0, >= 1.0.2) builder (3.2.3) @@ -139,12 +139,11 @@ GEM thread_safe (0.3.6) tzinfo (1.2.5) thread_safe (~> 0.1) - vault (0.12.0) + vault (0.13.0) aws-sigv4 websocket-driver (0.7.0) websocket-extensions (>= 0.1.0) websocket-extensions (0.1.3) - wwtd (1.3.0) PLATFORMS ruby @@ -156,10 +155,9 @@ DEPENDENCIES fc-vault-rails! pry rails (~> 5.2.0) - rake (~> 10.0) + rake rspec (~> 3.2) - sqlite3 (~> 1.3.6) - wwtd + sqlite3 (~> 1.3.13) BUNDLED WITH - 1.16.6 + 1.17.3 diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile new file mode 100644 index 00000000..e6b9af96 --- /dev/null +++ b/gemfiles/rails_6.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 6" +gem "sqlite3", "~> 1.4" + +gemspec path: "../" diff --git a/gemfiles/rails_6.gemfile.lock b/gemfiles/rails_6.gemfile.lock new file mode 100644 index 00000000..7d8ee873 --- /dev/null +++ b/gemfiles/rails_6.gemfile.lock @@ -0,0 +1,179 @@ +PATH + remote: .. + specs: + fc-vault-rails (2.0.3) + activerecord (>= 4.2) + vault (~> 0.7) + +GEM + remote: https://rubygems.org/ + specs: + actioncable (6.0.1) + actionpack (= 6.0.1) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailbox (6.0.1) + actionpack (= 6.0.1) + activejob (= 6.0.1) + activerecord (= 6.0.1) + activestorage (= 6.0.1) + activesupport (= 6.0.1) + mail (>= 2.7.1) + actionmailer (6.0.1) + actionpack (= 6.0.1) + actionview (= 6.0.1) + activejob (= 6.0.1) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (6.0.1) + actionview (= 6.0.1) + activesupport (= 6.0.1) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.0.1) + actionpack (= 6.0.1) + activerecord (= 6.0.1) + activestorage (= 6.0.1) + activesupport (= 6.0.1) + nokogiri (>= 1.8.5) + actionview (6.0.1) + activesupport (= 6.0.1) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.0.1) + activesupport (= 6.0.1) + globalid (>= 0.3.6) + activemodel (6.0.1) + activesupport (= 6.0.1) + activerecord (6.0.1) + activemodel (= 6.0.1) + activesupport (= 6.0.1) + activestorage (6.0.1) + actionpack (= 6.0.1) + activejob (= 6.0.1) + activerecord (= 6.0.1) + marcel (~> 0.3.1) + activesupport (6.0.1) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + zeitwerk (~> 2.2) + appraisal (2.2.0) + bundler + rake + thor (>= 0.14.0) + aws-eventstream (1.0.3) + aws-sigv4 (1.1.0) + aws-eventstream (~> 1.0, >= 1.0.2) + builder (3.2.3) + byebug (11.0.1) + coderay (1.1.2) + concurrent-ruby (1.1.5) + crass (1.0.5) + diff-lcs (1.3) + erubi (1.9.0) + globalid (0.4.2) + activesupport (>= 4.2.0) + i18n (1.7.0) + concurrent-ruby (~> 1.0) + loofah (2.4.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + mail (2.7.1) + mini_mime (>= 0.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (0.9.2) + mimemagic (0.3.3) + mini_mime (1.0.2) + mini_portile2 (2.4.0) + minitest (5.13.0) + nio4r (2.5.2) + nokogiri (1.10.5) + mini_portile2 (~> 2.4.0) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + rack (2.0.7) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails (6.0.1) + actioncable (= 6.0.1) + actionmailbox (= 6.0.1) + actionmailer (= 6.0.1) + actionpack (= 6.0.1) + actiontext (= 6.0.1) + actionview (= 6.0.1) + activejob (= 6.0.1) + activemodel (= 6.0.1) + activerecord (= 6.0.1) + activestorage (= 6.0.1) + activesupport (= 6.0.1) + bundler (>= 1.3.0) + railties (= 6.0.1) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (6.0.1) + actionpack (= 6.0.1) + activesupport (= 6.0.1) + method_source + rake (>= 0.8.7) + thor (>= 0.20.3, < 2.0) + rake (13.0.1) + rspec (3.9.0) + rspec-core (~> 3.9.0) + rspec-expectations (~> 3.9.0) + rspec-mocks (~> 3.9.0) + rspec-core (3.9.0) + rspec-support (~> 3.9.0) + rspec-expectations (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-mocks (3.9.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.9.0) + rspec-support (3.9.0) + sprockets (4.0.0) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.4.1) + thor (0.20.3) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + vault (0.13.0) + aws-sigv4 + websocket-driver (0.7.1) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.4) + zeitwerk (2.2.2) + +PLATFORMS + ruby + +DEPENDENCIES + appraisal (~> 2.1) + bundler + byebug + fc-vault-rails! + pry + rails (~> 6) + rake + rspec (~> 3.2) + sqlite3 (~> 1.4) + +BUNDLED WITH + 1.17.3 From e39ed30ad08531937b357474b27027022a27882b Mon Sep 17 00:00:00 2001 From: Kleber Correia Date: Mon, 2 Dec 2019 12:39:30 +0000 Subject: [PATCH 092/143] Bump version --- CHANGELOG.md | 7 +++++++ gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- gemfiles/rails_6.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80622eae..2a86b953 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,11 @@ # Vault Rails Changelog + +## 2.0.4 (December 2, 2019) + +IMPROVEMENTS +- Add Rails 6 Support +- Get rid of travis in the build pipeline + ## 2.0.3 (August 22, 2019) BUG FIXES diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 58361d76..1ba67e9f 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.3) + fc-vault-rails (2.0.4) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 83a3dff5..b968aa84 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.3) + fc-vault-rails (2.0.4) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index dfa6b316..a9ca18c2 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.3) + fc-vault-rails (2.0.4) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index bc96849f..0551d131 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.3) + fc-vault-rails (2.0.4) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_6.gemfile.lock b/gemfiles/rails_6.gemfile.lock index 7d8ee873..e89df5fe 100644 --- a/gemfiles/rails_6.gemfile.lock +++ b/gemfiles/rails_6.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.3) + fc-vault-rails (2.0.4) activerecord (>= 4.2) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 6a7163cb..e8b6554e 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.0.3" + VERSION = "2.0.4" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From ec5859e6a2e0eac6f3a45f5de66f755369c2ffe7 Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Thu, 2 Apr 2020 10:45:25 +0300 Subject: [PATCH 093/143] Update ruby versions in ci - ruby 2.4 - ruby 2.5 - ruby 2.6 - ruby 2.7 --- .circleci/config.yml | 64 +++++++++++++++++++++++++------------------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 64441002..c41d707f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,10 +2,7 @@ version: 2 defaults: &defaults docker: - - image: quay.io/fundingcircle/centos-ruby:ci-rvm - auth: - username: $DOCKER_USERNAME - password: $DOCKER_PASSWORD + - image: circleci/ruby:2.7 environment: RACK_ENV: test VAULT_VERSION: 0.10.4 @@ -15,6 +12,7 @@ aliases: RACK_ENV: test VAULT_VERSION: 0.10.4 command: &ruby-command | + gem install bundler:1.17.3 bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 bundle exec rake app:db:create bundle exec rake app:db:schema:load @@ -68,9 +66,9 @@ aliases: jobs: - test_ruby_2_3: + test_ruby_2_4: docker: - - image: circleci/ruby:2.3 + - image: circleci/ruby:2.4 environment: RACK_ENV: test VAULT_VERSION: 0.10.4 @@ -82,9 +80,9 @@ jobs: - run: *rails51 - run: *rails52 - test_ruby_2_4: + test_ruby_2_5: docker: - - image: circleci/ruby:2.4 + - image: circleci/ruby:2.5 environment: RACK_ENV: test VAULT_VERSION: 0.10.4 @@ -95,17 +93,31 @@ jobs: - run: *rails5 - run: *rails51 - run: *rails52 + - run: *rails6 - test_ruby_2_5: + test_ruby_2_6: docker: - - image: circleci/ruby:2.5 + - image: circleci/ruby:2.6 + environment: + RACK_ENV: test + VAULT_VERSION: 0.10.4 + steps: + - checkout + - run: *install-vault + - run: *rails5 + - run: *rails51 + - run: *rails52 + - run: *rails6 + + test_ruby_2_7: + docker: + - image: circleci/ruby:2.7 environment: RACK_ENV: test VAULT_VERSION: 0.10.4 steps: - checkout - run: *install-vault - - run: *rails4 - run: *rails5 - run: *rails51 - run: *rails52 @@ -116,8 +128,6 @@ jobs: steps: - checkout - - run: yum -y install curl - - run: yum clean all - run: name: Login to JFrog @@ -126,10 +136,6 @@ jobs: curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials chmod 600 ~/.gem/credentials - - run: - shell: /bin/bash -l - command: rvm use --default 2.5.1 - - run: name: Install Gem Versioner shell: /bin/bash -l @@ -153,8 +159,6 @@ jobs: steps: - checkout - - run: yum -y install curl - - run: yum clean all - run: name: Login to JFrog @@ -163,10 +167,6 @@ jobs: curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials chmod 600 ~/.gem/credentials - - run: - shell: /bin/bash -l - command: rvm use --default 2.5.1 - - run: name: Install Gem Versioner shell: /bin/bash -l @@ -190,19 +190,25 @@ workflows: test-and-release: jobs: - - test_ruby_2_3: + - test_ruby_2_4: context: org-global filters: tags: only: /.*/ - - test_ruby_2_4: + - test_ruby_2_5: context: org-global filters: tags: only: /.*/ - - test_ruby_2_5: + - test_ruby_2_6: + context: org-global + filters: + tags: + only: /.*/ + + - test_ruby_2_7: context: org-global filters: tags: @@ -216,9 +222,10 @@ workflows: tags: ignore: /.*/ requires: - - test_ruby_2_3 - test_ruby_2_4 - test_ruby_2_5 + - test_ruby_2_6 + - test_ruby_2_7 - publish-release: context: org-global @@ -228,6 +235,7 @@ workflows: tags: only: /^v[0-9]+(\.[0-9]+)*$/ requires: - - test_ruby_2_3 - test_ruby_2_4 - test_ruby_2_5 + - test_ruby_2_6 + - test_ruby_2_7 From 04648b2068260dccd49992a646e0ea89bd6c6db6 Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Thu, 2 Apr 2020 12:44:39 +0300 Subject: [PATCH 094/143] Bump bundler to 2.1.4 --- .circleci/config.yml | 32 ++++++++++++++------------------ gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- gemfiles/rails_6.gemfile.lock | 2 +- 6 files changed, 19 insertions(+), 23 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index c41d707f..9795e92d 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,18 +1,12 @@ version: 2 -defaults: &defaults - docker: - - image: circleci/ruby:2.7 - environment: - RACK_ENV: test - VAULT_VERSION: 0.10.4 - aliases: environment: &environment RACK_ENV: test VAULT_VERSION: 0.10.4 + BUNDLER_VERSION: 2.1.4 command: &ruby-command | - gem install bundler:1.17.3 + gem install bundler:$BUNDLER_VERSION bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 bundle exec rake app:db:create bundle exec rake app:db:schema:load @@ -70,8 +64,7 @@ jobs: docker: - image: circleci/ruby:2.4 environment: - RACK_ENV: test - VAULT_VERSION: 0.10.4 + <<: *environment steps: - checkout - run: *install-vault @@ -84,8 +77,7 @@ jobs: docker: - image: circleci/ruby:2.5 environment: - RACK_ENV: test - VAULT_VERSION: 0.10.4 + <<: *environment steps: - checkout - run: *install-vault @@ -99,8 +91,7 @@ jobs: docker: - image: circleci/ruby:2.6 environment: - RACK_ENV: test - VAULT_VERSION: 0.10.4 + <<: *environment steps: - checkout - run: *install-vault @@ -113,8 +104,7 @@ jobs: docker: - image: circleci/ruby:2.7 environment: - RACK_ENV: test - VAULT_VERSION: 0.10.4 + <<: *environment steps: - checkout - run: *install-vault @@ -124,7 +114,10 @@ jobs: - run: *rails6 publish-pre-release: - <<: *defaults + docker: + - image: circleci/ruby:2.7 + environment: + <<: *environment steps: - checkout @@ -155,7 +148,10 @@ jobs: gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases publish-release: - <<: *defaults + docker: + - image: circleci/ruby:2.7 + environment: + <<: *environment steps: - checkout diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index 1ba67e9f..dc49073a 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -146,4 +146,4 @@ DEPENDENCIES sqlite3 (~> 1.3.13) BUNDLED WITH - 1.17.3 + 2.1.2 diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index b968aa84..2019a9d4 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -152,4 +152,4 @@ DEPENDENCIES sqlite3 (~> 1.3.13) BUNDLED WITH - 1.17.3 + 2.1.2 diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index a9ca18c2..fdcbda30 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -152,4 +152,4 @@ DEPENDENCIES sqlite3 (~> 1.3.13) BUNDLED WITH - 1.17.3 + 2.1.2 diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 0551d131..2a7c0ce0 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -160,4 +160,4 @@ DEPENDENCIES sqlite3 (~> 1.3.13) BUNDLED WITH - 1.17.3 + 2.1.2 diff --git a/gemfiles/rails_6.gemfile.lock b/gemfiles/rails_6.gemfile.lock index e89df5fe..e3009ab8 100644 --- a/gemfiles/rails_6.gemfile.lock +++ b/gemfiles/rails_6.gemfile.lock @@ -176,4 +176,4 @@ DEPENDENCIES sqlite3 (~> 1.4) BUNDLED WITH - 1.17.3 + 2.1.2 From 13c29ccb8a0b8dd67b43eade88471848ab3544aa Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Mon, 19 Oct 2020 17:22:56 +0300 Subject: [PATCH 095/143] Do not pollute record changes when loading attributes for the first time This is done as in rails >= 5.2 the lock! & with_lock methods now require that the record doesn't have any changes otherwise they'll raise an error. https://github.com/rails/rails/blob/v5.2.4.4/activerecord/lib/active_record/locking/pessimistic.rb#L65 ``` def lock!(lock = true) if persisted? if has_changes_to_save? raise(<<-MSG.squish) Locking a record with unpersisted changes is not supported. Use `save` to persist the changes, or `reload` to discard them explicitly. MSG end reload(lock: lock) end self end ``` Apparently clear_attribute_changes receives an array of attributes which it should clear. https://apidock.com/rails/v5.2.3/ActiveModel/Dirty/clear_attribute_changes --- lib/vault/latest/encrypted_model.rb | 2 +- spec/integration/rails_spec.rb | 33 +++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb index 8e884543..40b22fd1 100644 --- a/lib/vault/latest/encrypted_model.rb +++ b/lib/vault/latest/encrypted_model.rb @@ -329,7 +329,7 @@ def __vault_load_attribute!(attribute, options) __vault_loaded_attributes << attribute # Write the virtual attribute with the plaintext value - write_attribute(attribute, plaintext) + write_attribute(attribute, plaintext).tap { clear_attribute_changes([attribute]) } end # Encrypt all the attributes using Vault and set the encrypted values back diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 9ad8c8a0..8c5eeebd 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -40,6 +40,12 @@ expect(person.ssn_was).to eq("123-45-6789") end + it 'does not pollute changes / dirty attributes / when loading a record from the db' do + Person.create!(ssn: "123-45-6789") + + expect(Person.last.changes).to be_blank + end + it "allows attributes to be updated with nil values" do person = Person.create!(ssn: "123-45-6789") person.update_attributes!(ssn: nil) @@ -175,6 +181,15 @@ expect(person.ssn_was).to eq("123-45-6789") end + it 'does not pollute changes / dirty attributes / when loading a record from the db' do + Person.create!(ssn: "123-45-6789") + + person = Person.last + expect(person.changes).to be_blank + expect(person.ssn).to eq('123-45-6789') + expect(person.changes).to be_blank + end + it "allows attributes to be unset" do person = LazyPerson.create!(ssn: "123-45-6789") person.update_attributes!(ssn: nil) @@ -572,7 +587,7 @@ context 'attribute with defined serializer' do context 'new record with duplicated IP address' do it 'is invalid' do - person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) + Person.create!(ip_address: IPAddr.new('127.0.0.1')) same_ip_address_person = Person.new(ip_address: IPAddr.new('127.0.0.1')) expect(same_ip_address_person).not_to be_valid @@ -670,8 +685,8 @@ it 'finds the expected record' do first_person = LazyPerson.create!(passport_number: '12345678') - second_person = LazyPerson.create!(passport_number: '12345678') - third_person = LazyPerson.create!(passport_number: '87654321') + LazyPerson.create!(passport_number: '12345678') + LazyPerson.create!(passport_number: '87654321') expect(LazyPerson.encrypted_find_by(passport_number: '12345678')).to eq(first_person) end @@ -679,7 +694,7 @@ context 'searching by attributes with defined serializer' do it 'finds the expected record' do first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) - second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + Person.create!(ip_address: IPAddr.new('192.168.0.1')) expect(Person.encrypted_find_by(ip_address: IPAddr.new('127.0.0.1'))).to eq(first_person) end @@ -707,8 +722,8 @@ it 'finds the expected record' do first_person = LazyPerson.create!(passport_number: '12345678') - second_person = LazyPerson.create!(passport_number: '12345678') - third_person = LazyPerson.create!(passport_number: '87654321') + LazyPerson.create!(passport_number: '12345678') + LazyPerson.create!(passport_number: '87654321') expect(LazyPerson.encrypted_find_by!(passport_number: '12345678')).to eq(first_person) end @@ -716,7 +731,7 @@ context 'searching by attributes with defined serializer' do it 'finds the expected record' do first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) - second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + Person.create!(ip_address: IPAddr.new('192.168.0.1')) expect(Person.encrypted_find_by!(ip_address: IPAddr.new('127.0.0.1'))).to eq(first_person) end @@ -751,7 +766,7 @@ it 'finds the expected records' do first_person = LazyPerson.create!(passport_number: '12345678') second_person = LazyPerson.create!(passport_number: '12345678') - third_person = LazyPerson.create!(passport_number: '87654321') + LazyPerson.create!(passport_number: '87654321') expect(LazyPerson.encrypted_where(passport_number: '12345678').pluck(:id)).to match_array([first_person, second_person].map(&:id)) end @@ -759,7 +774,7 @@ context 'searching by attributes with defined serializer' do it 'finds the expected records' do first_person = Person.create!(ip_address: IPAddr.new('127.0.0.1')) - second_person = Person.create!(ip_address: IPAddr.new('192.168.0.1')) + Person.create!(ip_address: IPAddr.new('192.168.0.1')) expect(Person.encrypted_where(ip_address: IPAddr.new('127.0.0.1')).pluck(:id)).to match_array([first_person.id]) end From 12349291303a3adcc00a1e53ef4bd0cbabd169e0 Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Mon, 19 Oct 2020 17:54:41 +0300 Subject: [PATCH 096/143] Bump version to 2.0.5 --- CHANGELOG.md | 7 +++++++ gemfiles/rails_4.2.gemfile.lock | 2 +- gemfiles/rails_5.0.gemfile.lock | 2 +- gemfiles/rails_5.1.gemfile.lock | 2 +- gemfiles/rails_5.2.gemfile.lock | 2 +- gemfiles/rails_6.gemfile.lock | 2 +- lib/vault/rails/version.rb | 2 +- 7 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a86b953..6837f84b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Vault Rails Changelog +## 2.0.5 (October 19, 2020) + +- Fix compatibility with `#with_lock` / `#lock!` - on initialization the `#changes` is no longer polluted. Fixed error: +``` +RuntimeError: Locking a record with unpersisted changes is not supported. Use `save` to persist the changes, or `reload` to discard them explicitly. +``` + ## 2.0.4 (December 2, 2019) IMPROVEMENTS diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock index dc49073a..3095d47c 100644 --- a/gemfiles/rails_4.2.gemfile.lock +++ b/gemfiles/rails_4.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.4) + fc-vault-rails (2.0.5) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock index 2019a9d4..e72770ba 100644 --- a/gemfiles/rails_5.0.gemfile.lock +++ b/gemfiles/rails_5.0.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.4) + fc-vault-rails (2.0.5) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock index fdcbda30..c631f523 100644 --- a/gemfiles/rails_5.1.gemfile.lock +++ b/gemfiles/rails_5.1.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.4) + fc-vault-rails (2.0.5) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock index 2a7c0ce0..8214692d 100644 --- a/gemfiles/rails_5.2.gemfile.lock +++ b/gemfiles/rails_5.2.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.4) + fc-vault-rails (2.0.5) activerecord (>= 4.2) vault (~> 0.7) diff --git a/gemfiles/rails_6.gemfile.lock b/gemfiles/rails_6.gemfile.lock index e3009ab8..ce3750f3 100644 --- a/gemfiles/rails_6.gemfile.lock +++ b/gemfiles/rails_6.gemfile.lock @@ -1,7 +1,7 @@ PATH remote: .. specs: - fc-vault-rails (2.0.4) + fc-vault-rails (2.0.5) activerecord (>= 4.2) vault (~> 0.7) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index e8b6554e..0fd157dc 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.0.4" + VERSION = "2.0.5" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From d0bc93e2c559d9a362674dacba6b1f024b07f020 Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Thu, 2 Apr 2020 16:16:07 +0300 Subject: [PATCH 097/143] Test bug in rails 5 db:create / assets:precompile When running db:create or assets:precompile the gem does a db connection for no reason in rails 5.x. In rails 4 it is not doing it. The bug happens here: https://github.com/rails/rails/blob/v5.2.6/activerecord/lib/active_record/type.rb#L41 ``` def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc: ``` `current_adapter_name` for some reason does a db connection. --- .circleci/config.yml | 2 - .gitignore | 1 + Appraisals | 25 ++-- CHANGELOG.md | 4 + gemfiles/rails_4.2.gemfile | 1 + gemfiles/rails_4.2.gemfile.lock | 149 ------------------- gemfiles/rails_5.0.gemfile.lock | 155 -------------------- gemfiles/rails_5.1.gemfile.lock | 155 -------------------- gemfiles/rails_5.2.gemfile.lock | 163 --------------------- gemfiles/rails_6.gemfile.lock | 179 ----------------------- lib/vault/latest/encrypted_model.rb | 1 + lib/vault/rails/version.rb | 2 +- spec/dummy/config/database.yml | 4 +- spec/dummy/config/initializers/vault.rb | 4 +- spec/integration/rails_db_create_spec.rb | 24 +++ spec/integration/rails_spec.rb | 20 +-- spec/spec_helper.rb | 14 +- 17 files changed, 74 insertions(+), 829 deletions(-) delete mode 100644 gemfiles/rails_4.2.gemfile.lock delete mode 100644 gemfiles/rails_5.0.gemfile.lock delete mode 100644 gemfiles/rails_5.1.gemfile.lock delete mode 100644 gemfiles/rails_5.2.gemfile.lock delete mode 100644 gemfiles/rails_6.gemfile.lock create mode 100644 spec/integration/rails_db_create_spec.rb diff --git a/.circleci/config.yml b/.circleci/config.yml index 9795e92d..85f67ef3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,9 +4,7 @@ aliases: environment: &environment RACK_ENV: test VAULT_VERSION: 0.10.4 - BUNDLER_VERSION: 2.1.4 command: &ruby-command | - gem install bundler:$BUNDLER_VERSION bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 bundle exec rake app:db:create bundle exec rake app:db:schema:load diff --git a/.gitignore b/.gitignore index f94de97f..f21a75c9 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ build/ Gemfile.lock .ruby-version .ruby-gemset +gemfiles/*.gemfile.lock # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc diff --git a/Appraisals b/Appraisals index 6b25703f..3d03c8ac 100644 --- a/Appraisals +++ b/Appraisals @@ -1,20 +1,25 @@ -appraise "rails-4.2" do - gem "rails", "~> 4.2.0" +appraise 'rails-4.2' do + gem 'rails', '~> 4.2.0' gem 'sqlite3', '~> 1.3.13' + gem 'bundler', '~> 1.17.3' end -appraise "rails-5.0" do - gem "rails", "~> 5.0.0" + +appraise 'rails-5.0' do + gem 'rails', '~> 5.0.0' gem 'sqlite3', '~> 1.3.13' end -appraise "rails-5.1" do - gem "rails", "~> 5.1.0" + +appraise 'rails-5.1' do + gem 'rails', '~> 5.1.0' gem 'sqlite3', '~> 1.3.13' end -appraise "rails-5.2" do - gem "rails", "~> 5.2.0" + +appraise 'rails-5.2' do + gem 'rails', '~> 5.2.0' gem 'sqlite3', '~> 1.3.13' end -appraise "rails-6" do - gem "rails", "~> 6" + +appraise 'rails-6' do + gem 'rails', '~> 6' gem 'sqlite3', '~> 1.4' end diff --git a/CHANGELOG.md b/CHANGELOG.md index 6837f84b..298dd33a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Vault Rails Changelog +## 2.1.0 (January 11, 2022) + +Prevent db queries on boot -> so that db:create / assets:precompile work + ## 2.0.5 (October 19, 2020) - Fix compatibility with `#with_lock` / `#lock!` - on initialization the `#changes` is no longer polluted. Fixed error: diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 07b883b7..510b3205 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" gem "sqlite3", "~> 1.3.13" +gem "bundler", "~> 1.17.3" gemspec path: "../" diff --git a/gemfiles/rails_4.2.gemfile.lock b/gemfiles/rails_4.2.gemfile.lock deleted file mode 100644 index 3095d47c..00000000 --- a/gemfiles/rails_4.2.gemfile.lock +++ /dev/null @@ -1,149 +0,0 @@ -PATH - remote: .. - specs: - fc-vault-rails (2.0.5) - activerecord (>= 4.2) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.2.11.1) - actionpack (= 4.2.11.1) - actionview (= 4.2.11.1) - activejob (= 4.2.11.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.11.1) - actionview (= 4.2.11.1) - activesupport (= 4.2.11.1) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.11.1) - activesupport (= 4.2.11.1) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (4.2.11.1) - activesupport (= 4.2.11.1) - globalid (>= 0.3.0) - activemodel (4.2.11.1) - activesupport (= 4.2.11.1) - builder (~> 3.1) - activerecord (4.2.11.1) - activemodel (= 4.2.11.1) - activesupport (= 4.2.11.1) - arel (~> 6.0) - activesupport (4.2.11.1) - i18n (~> 0.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - arel (6.0.4) - aws-eventstream (1.0.3) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - builder (3.2.3) - byebug (11.0.1) - coderay (1.1.2) - concurrent-ruby (1.1.5) - crass (1.0.4) - diff-lcs (1.3) - erubis (2.7.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (0.9.5) - concurrent-ruby (~> 1.0) - loofah (2.2.3) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - method_source (0.9.2) - mini_mime (1.0.1) - mini_portile2 (2.4.0) - minitest (5.11.3) - nokogiri (1.10.2) - mini_portile2 (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - rack (1.6.11) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.11.1) - actionmailer (= 4.2.11.1) - actionpack (= 4.2.11.1) - actionview (= 4.2.11.1) - activejob (= 4.2.11.1) - activemodel (= 4.2.11.1) - activerecord (= 4.2.11.1) - activesupport (= 4.2.11.1) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.11.1) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.9) - activesupport (>= 4.2.0, < 5.0) - nokogiri (~> 1.6) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (4.2.11.1) - actionpack (= 4.2.11.1) - activesupport (= 4.2.11.1) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (10.5.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.3.13) - thor (0.20.3) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - vault (0.13.0) - aws-sigv4 - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - byebug - fc-vault-rails! - pry - rails (~> 4.2.0) - rake - rspec (~> 3.2) - sqlite3 (~> 1.3.13) - -BUNDLED WITH - 2.1.2 diff --git a/gemfiles/rails_5.0.gemfile.lock b/gemfiles/rails_5.0.gemfile.lock deleted file mode 100644 index e72770ba..00000000 --- a/gemfiles/rails_5.0.gemfile.lock +++ /dev/null @@ -1,155 +0,0 @@ -PATH - remote: .. - specs: - fc-vault-rails (2.0.5) - activerecord (>= 4.2) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.0.7.1) - actionpack (= 5.0.7.1) - nio4r (>= 1.2, < 3.0) - websocket-driver (~> 0.6.1) - actionmailer (5.0.7.1) - actionpack (= 5.0.7.1) - actionview (= 5.0.7.1) - activejob (= 5.0.7.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.0.7.1) - actionview (= 5.0.7.1) - activesupport (= 5.0.7.1) - rack (~> 2.0) - rack-test (~> 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.0.7.1) - activesupport (= 5.0.7.1) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.0.7.1) - activesupport (= 5.0.7.1) - globalid (>= 0.3.6) - activemodel (5.0.7.1) - activesupport (= 5.0.7.1) - activerecord (5.0.7.1) - activemodel (= 5.0.7.1) - activesupport (= 5.0.7.1) - arel (~> 7.0) - activesupport (5.0.7.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - arel (7.1.4) - aws-eventstream (1.0.3) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - builder (3.2.3) - byebug (11.0.0) - coderay (1.1.2) - concurrent-ruby (1.1.4) - crass (1.0.4) - diff-lcs (1.3) - erubis (2.7.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.5.3) - concurrent-ruby (~> 1.0) - loofah (2.2.3) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - method_source (0.9.2) - mini_mime (1.0.1) - mini_portile2 (2.4.0) - minitest (5.11.3) - nio4r (2.3.1) - nokogiri (1.10.1) - mini_portile2 (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - rack (2.0.6) - rack-test (0.6.3) - rack (>= 1.0) - rails (5.0.7.1) - actioncable (= 5.0.7.1) - actionmailer (= 5.0.7.1) - actionpack (= 5.0.7.1) - actionview (= 5.0.7.1) - activejob (= 5.0.7.1) - activemodel (= 5.0.7.1) - activerecord (= 5.0.7.1) - activesupport (= 5.0.7.1) - bundler (>= 1.3.0) - railties (= 5.0.7.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.0.7.1) - actionpack (= 5.0.7.1) - activesupport (= 5.0.7.1) - method_source - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (10.5.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.3.13) - thor (0.20.3) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - vault (0.13.0) - aws-sigv4 - websocket-driver (0.6.5) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - byebug - fc-vault-rails! - pry - rails (~> 5.0.0) - rake - rspec (~> 3.2) - sqlite3 (~> 1.3.13) - -BUNDLED WITH - 2.1.2 diff --git a/gemfiles/rails_5.1.gemfile.lock b/gemfiles/rails_5.1.gemfile.lock deleted file mode 100644 index c631f523..00000000 --- a/gemfiles/rails_5.1.gemfile.lock +++ /dev/null @@ -1,155 +0,0 @@ -PATH - remote: .. - specs: - fc-vault-rails (2.0.5) - activerecord (>= 4.2) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.1.6.1) - actionpack (= 5.1.6.1) - nio4r (~> 2.0) - websocket-driver (~> 0.6.1) - actionmailer (5.1.6.1) - actionpack (= 5.1.6.1) - actionview (= 5.1.6.1) - activejob (= 5.1.6.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.1.6.1) - actionview (= 5.1.6.1) - activesupport (= 5.1.6.1) - rack (~> 2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.6.1) - activesupport (= 5.1.6.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.1.6.1) - activesupport (= 5.1.6.1) - globalid (>= 0.3.6) - activemodel (5.1.6.1) - activesupport (= 5.1.6.1) - activerecord (5.1.6.1) - activemodel (= 5.1.6.1) - activesupport (= 5.1.6.1) - arel (~> 8.0) - activesupport (5.1.6.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - arel (8.0.0) - aws-eventstream (1.0.3) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - builder (3.2.3) - byebug (11.0.0) - coderay (1.1.2) - concurrent-ruby (1.1.4) - crass (1.0.4) - diff-lcs (1.3) - erubi (1.8.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.5.3) - concurrent-ruby (~> 1.0) - loofah (2.2.3) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - method_source (0.9.2) - mini_mime (1.0.1) - mini_portile2 (2.4.0) - minitest (5.11.3) - nio4r (2.3.1) - nokogiri (1.10.1) - mini_portile2 (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - rack (2.0.6) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (5.1.6.1) - actioncable (= 5.1.6.1) - actionmailer (= 5.1.6.1) - actionpack (= 5.1.6.1) - actionview (= 5.1.6.1) - activejob (= 5.1.6.1) - activemodel (= 5.1.6.1) - activerecord (= 5.1.6.1) - activesupport (= 5.1.6.1) - bundler (>= 1.3.0) - railties (= 5.1.6.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.1.6.1) - actionpack (= 5.1.6.1) - activesupport (= 5.1.6.1) - method_source - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (10.5.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.3.13) - thor (0.20.3) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - vault (0.13.0) - aws-sigv4 - websocket-driver (0.6.5) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - byebug - fc-vault-rails! - pry - rails (~> 5.1.0) - rake - rspec (~> 3.2) - sqlite3 (~> 1.3.13) - -BUNDLED WITH - 2.1.2 diff --git a/gemfiles/rails_5.2.gemfile.lock b/gemfiles/rails_5.2.gemfile.lock deleted file mode 100644 index 8214692d..00000000 --- a/gemfiles/rails_5.2.gemfile.lock +++ /dev/null @@ -1,163 +0,0 @@ -PATH - remote: .. - specs: - fc-vault-rails (2.0.5) - activerecord (>= 4.2) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (5.2.2) - actionpack (= 5.2.2) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailer (5.2.2) - actionpack (= 5.2.2) - actionview (= 5.2.2) - activejob (= 5.2.2) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (5.2.2) - actionview (= 5.2.2) - activesupport (= 5.2.2) - rack (~> 2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.2.2) - activesupport (= 5.2.2) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.2.2) - activesupport (= 5.2.2) - globalid (>= 0.3.6) - activemodel (5.2.2) - activesupport (= 5.2.2) - activerecord (5.2.2) - activemodel (= 5.2.2) - activesupport (= 5.2.2) - arel (>= 9.0) - activestorage (5.2.2) - actionpack (= 5.2.2) - activerecord (= 5.2.2) - marcel (~> 0.3.1) - activesupport (5.2.2) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - arel (9.0.0) - aws-eventstream (1.0.3) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - builder (3.2.3) - byebug (11.0.0) - coderay (1.1.2) - concurrent-ruby (1.1.4) - crass (1.0.4) - diff-lcs (1.3) - erubi (1.8.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.5.3) - concurrent-ruby (~> 1.0) - loofah (2.2.3) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mimemagic (0.3.3) - mini_mime (1.0.1) - mini_portile2 (2.4.0) - minitest (5.11.3) - nio4r (2.3.1) - nokogiri (1.10.1) - mini_portile2 (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - rack (2.0.6) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (5.2.2) - actioncable (= 5.2.2) - actionmailer (= 5.2.2) - actionpack (= 5.2.2) - actionview (= 5.2.2) - activejob (= 5.2.2) - activemodel (= 5.2.2) - activerecord (= 5.2.2) - activestorage (= 5.2.2) - activesupport (= 5.2.2) - bundler (>= 1.3.0) - railties (= 5.2.2) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.0.4) - loofah (~> 2.2, >= 2.2.2) - railties (5.2.2) - actionpack (= 5.2.2) - activesupport (= 5.2.2) - method_source - rake (>= 0.8.7) - thor (>= 0.19.0, < 2.0) - rake (10.5.0) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - sprockets (3.7.2) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.3.13) - thor (0.20.3) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - vault (0.13.0) - aws-sigv4 - websocket-driver (0.7.0) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - byebug - fc-vault-rails! - pry - rails (~> 5.2.0) - rake - rspec (~> 3.2) - sqlite3 (~> 1.3.13) - -BUNDLED WITH - 2.1.2 diff --git a/gemfiles/rails_6.gemfile.lock b/gemfiles/rails_6.gemfile.lock deleted file mode 100644 index ce3750f3..00000000 --- a/gemfiles/rails_6.gemfile.lock +++ /dev/null @@ -1,179 +0,0 @@ -PATH - remote: .. - specs: - fc-vault-rails (2.0.5) - activerecord (>= 4.2) - vault (~> 0.7) - -GEM - remote: https://rubygems.org/ - specs: - actioncable (6.0.1) - actionpack (= 6.0.1) - nio4r (~> 2.0) - websocket-driver (>= 0.6.1) - actionmailbox (6.0.1) - actionpack (= 6.0.1) - activejob (= 6.0.1) - activerecord (= 6.0.1) - activestorage (= 6.0.1) - activesupport (= 6.0.1) - mail (>= 2.7.1) - actionmailer (6.0.1) - actionpack (= 6.0.1) - actionview (= 6.0.1) - activejob (= 6.0.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 2.0) - actionpack (6.0.1) - actionview (= 6.0.1) - activesupport (= 6.0.1) - rack (~> 2.0) - rack-test (>= 0.6.3) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.0.1) - actionpack (= 6.0.1) - activerecord (= 6.0.1) - activestorage (= 6.0.1) - activesupport (= 6.0.1) - nokogiri (>= 1.8.5) - actionview (6.0.1) - activesupport (= 6.0.1) - builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activejob (6.0.1) - activesupport (= 6.0.1) - globalid (>= 0.3.6) - activemodel (6.0.1) - activesupport (= 6.0.1) - activerecord (6.0.1) - activemodel (= 6.0.1) - activesupport (= 6.0.1) - activestorage (6.0.1) - actionpack (= 6.0.1) - activejob (= 6.0.1) - activerecord (= 6.0.1) - marcel (~> 0.3.1) - activesupport (6.0.1) - concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (>= 0.7, < 2) - minitest (~> 5.1) - tzinfo (~> 1.1) - zeitwerk (~> 2.2) - appraisal (2.2.0) - bundler - rake - thor (>= 0.14.0) - aws-eventstream (1.0.3) - aws-sigv4 (1.1.0) - aws-eventstream (~> 1.0, >= 1.0.2) - builder (3.2.3) - byebug (11.0.1) - coderay (1.1.2) - concurrent-ruby (1.1.5) - crass (1.0.5) - diff-lcs (1.3) - erubi (1.9.0) - globalid (0.4.2) - activesupport (>= 4.2.0) - i18n (1.7.0) - concurrent-ruby (~> 1.0) - loofah (2.4.0) - crass (~> 1.0.2) - nokogiri (>= 1.5.9) - mail (2.7.1) - mini_mime (>= 0.1.1) - marcel (0.3.3) - mimemagic (~> 0.3.2) - method_source (0.9.2) - mimemagic (0.3.3) - mini_mime (1.0.2) - mini_portile2 (2.4.0) - minitest (5.13.0) - nio4r (2.5.2) - nokogiri (1.10.5) - mini_portile2 (~> 2.4.0) - pry (0.12.2) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - rack (2.0.7) - rack-test (1.1.0) - rack (>= 1.0, < 3) - rails (6.0.1) - actioncable (= 6.0.1) - actionmailbox (= 6.0.1) - actionmailer (= 6.0.1) - actionpack (= 6.0.1) - actiontext (= 6.0.1) - actionview (= 6.0.1) - activejob (= 6.0.1) - activemodel (= 6.0.1) - activerecord (= 6.0.1) - activestorage (= 6.0.1) - activesupport (= 6.0.1) - bundler (>= 1.3.0) - railties (= 6.0.1) - sprockets-rails (>= 2.0.0) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) - nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) - loofah (~> 2.3) - railties (6.0.1) - actionpack (= 6.0.1) - activesupport (= 6.0.1) - method_source - rake (>= 0.8.7) - thor (>= 0.20.3, < 2.0) - rake (13.0.1) - rspec (3.9.0) - rspec-core (~> 3.9.0) - rspec-expectations (~> 3.9.0) - rspec-mocks (~> 3.9.0) - rspec-core (3.9.0) - rspec-support (~> 3.9.0) - rspec-expectations (3.9.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-mocks (3.9.0) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.9.0) - rspec-support (3.9.0) - sprockets (4.0.0) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.1) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - sqlite3 (1.4.1) - thor (0.20.3) - thread_safe (0.3.6) - tzinfo (1.2.5) - thread_safe (~> 0.1) - vault (0.13.0) - aws-sigv4 - websocket-driver (0.7.1) - websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.4) - zeitwerk (2.2.2) - -PLATFORMS - ruby - -DEPENDENCIES - appraisal (~> 2.1) - bundler - byebug - fc-vault-rails! - pry - rails (~> 6) - rake - rspec (~> 3.2) - sqlite3 (~> 1.4) - -BUNDLED WITH - 2.1.2 diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb index 40b22fd1..cc4b8b50 100644 --- a/lib/vault/latest/encrypted_model.rb +++ b/lib/vault/latest/encrypted_model.rb @@ -164,6 +164,7 @@ def _vault_validate_options!(options) end def _vault_fetch_attribute_type(options) + puts "Attribute Type: ; #{options.inspect}" attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) if attribute_type.is_a?(Symbol) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 0fd157dc..8ccd1eba 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.0.5" + VERSION = "2.1.0" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') diff --git a/spec/dummy/config/database.yml b/spec/dummy/config/database.yml index 0b956764..99a96a21 100644 --- a/spec/dummy/config/database.yml +++ b/spec/dummy/config/database.yml @@ -5,8 +5,8 @@ default: &default development: <<: *default - database: db/development.sqlite3 + database: <%= ENV['FC_VAULT_RAILS_DUMMY_DATABASE_PATH'] || 'db/development.sqlite3' %> test: <<: *default - database: db/test.sqlite3 + database: 'db/test.sqlite3' diff --git a/spec/dummy/config/initializers/vault.rb b/spec/dummy/config/initializers/vault.rb index 2f4b66bb..ff7867ff 100644 --- a/spec/dummy/config/initializers/vault.rb +++ b/spec/dummy/config/initializers/vault.rb @@ -5,7 +5,7 @@ Vault::Rails.configure do |vault| vault.application = "dummy" - vault.address = RSpec::VaultServer.address - vault.token = RSpec::VaultServer.token + vault.address = ENV['FC_VAULT_RAILS_DUMMY_VAULT_SERVER'] || RSpec::VaultServer.address + vault.token = ENV['FC_VAULT_RAILS_DUMMY_VAULT_TOKEN'] || RSpec::VaultServer.token vault.enabled = true end diff --git a/spec/integration/rails_db_create_spec.rb b/spec/integration/rails_db_create_spec.rb new file mode 100644 index 00000000..1e8eb7fe --- /dev/null +++ b/spec/integration/rails_db_create_spec.rb @@ -0,0 +1,24 @@ +# encoding: utf-8 + +require "spec_helper" + +RSpec.describe './bin/rake db:create' do + it "works == the code doesn't need a database to load" do + db_file = File.join(dummy_root, 'db/rails_db_create_spec.sqlite3') + + File.delete(db_file) if File.exist?(db_file) + + command = [ + 'RAILS_ENV=development', + 'FC_VAULT_RAILS_DUMMY_DATABASE_PATH="db/rails_db_create_spec.sqlite3"', + "FC_VAULT_RAILS_DUMMY_VAULT_SERVER='#{RSpec::VaultServer.address}'", + "FC_VAULT_RAILS_DUMMY_VAULT_TOKEN='#{RSpec::VaultServer.token}'", + "#{dummy_root}/bin/rails runner 'puts TypedPerson.class'" + ] + + `#{command.join(' ')}` + + # If the file exists it means that rails tried to connect to the database + expect(File.exist?(db_file)).to eq(false) + end +end diff --git a/spec/integration/rails_spec.rb b/spec/integration/rails_spec.rb index 8c5eeebd..e1d63c8e 100644 --- a/spec/integration/rails_spec.rb +++ b/spec/integration/rails_spec.rb @@ -48,7 +48,7 @@ it "allows attributes to be updated with nil values" do person = Person.create!(ssn: "123-45-6789") - person.update_attributes!(ssn: nil) + person.update!(ssn: nil) person.reload expect(person.ssn).to be(nil) @@ -69,7 +69,7 @@ it "allows attributes to be unset after reload" do person = Person.create!(ssn: "123-45-6789") person.reload - person.update_attributes!(ssn: nil) + person.update!(ssn: nil) person.reload expect(person.ssn).to be(nil) @@ -77,7 +77,7 @@ it "allows attributes to be blank" do person = Person.create!(ssn: "123-45-6789") - person.update_attributes!(ssn: "") + person.update!(ssn: "") person.reload expect(person.ssn).to eq("") @@ -192,7 +192,7 @@ it "allows attributes to be unset" do person = LazyPerson.create!(ssn: "123-45-6789") - person.update_attributes!(ssn: nil) + person.update!(ssn: nil) person.reload expect(person.ssn).to be(nil) @@ -207,7 +207,7 @@ it "allows attributes to be unset after reload" do person = LazyPerson.create!(ssn: "123-45-6789") person.reload - person.update_attributes!(ssn: nil) + person.update!(ssn: nil) person.reload expect(person.ssn).to be(nil) @@ -221,7 +221,7 @@ it "allows attributes to be blank" do person = LazyPerson.create!(ssn: "123-45-6789") - person.update_attributes!(ssn: "") + person.update!(ssn: "") person.reload expect(person.ssn).to eq("") @@ -287,7 +287,7 @@ it "allows attributes to be unset" do person = Person.create!(credit_card: "1234567890111213") - person.update_attributes!(credit_card: nil) + person.update!(credit_card: nil) person.reload expect(person.credit_card).to be(nil) @@ -295,7 +295,7 @@ it "allows attributes to be blank" do person = Person.create!(credit_card: "1234567890111213") - person.update_attributes!(credit_card: "") + person.update!(credit_card: "") person.reload expect(person.credit_card).to eq("") @@ -337,7 +337,7 @@ it "allows attributes to be unset" do person = Person.create!(non_ascii: "dás ümlaut") - person.update_attributes!(non_ascii: nil) + person.update!(non_ascii: nil) person.reload expect(person.non_ascii).to be(nil) @@ -345,7 +345,7 @@ it "allows attributes to be blank" do person = Person.create!(non_ascii: "dás ümlaut") - person.update_attributes!(non_ascii: "") + person.update!(non_ascii: "") person.reload expect(person.non_ascii).to eq("") diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2f9dfdaf..891d2196 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,21 @@ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) -require "vault/rails" +require "vault/rails" require "rspec" +module PathHelpers + def project_root + @project_root ||= File.expand_path('../..', __FILE__) + end + + def dummy_root + File.join(project_root, 'spec', 'dummy') + end +end + RSpec.configure do |config| + config.include PathHelpers + # Prohibit using the should syntax config.expect_with :rspec do |spec| spec.syntax = :expect From 006f61cf000f0a55aa6623582118b680f09afe9a Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Tue, 11 Jan 2022 14:48:22 +0200 Subject: [PATCH 098/143] Prevent db connection on boot The lookup call tries to establish connection to the database when the application code is loading. This bugs rake tasks like db:create / assets:precompile https://github.com/rails/rails/blob/v5.2.6/activerecord/lib/active_record/type.rb#L41 ``` def lookup(*args, adapter: current_adapter_name, **kwargs) # :nodoc: ``` The `current_adapter_name` for some reason does a db connection. The code is change to getting the current adapter with `ActiveRecord::Base.connection_config[:adapter]`, which is not making a db connection. --- lib/vault/latest/encrypted_model.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb index cc4b8b50..1bdd12ab 100644 --- a/lib/vault/latest/encrypted_model.rb +++ b/lib/vault/latest/encrypted_model.rb @@ -164,11 +164,14 @@ def _vault_validate_options!(options) end def _vault_fetch_attribute_type(options) - puts "Attribute Type: ; #{options.inspect}" attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) if attribute_type.is_a?(Symbol) - ActiveRecord::Type.lookup(attribute_type) + if ActiveRecord::Base.connection_config[:adapter] + ActiveRecord::Type.lookup(attribute_type, adapter: ActiveRecord::Base.connection_config[:adapter]) + else + ActiveRecord::Type.lookup(attribute_type) # This call does a db connection, best find a way to configure `ActiveRecord::Base.connection_config[:adapter]` + end else ActiveModel::Type::Value.new end From a92bfbaf79c07691a6ed389f83a5a8b7c38771ab Mon Sep 17 00:00:00 2001 From: Ivan Dimov Date: Thu, 27 Jan 2022 09:44:35 +0200 Subject: [PATCH 099/143] Export VaultTransitJsonCodec from fca --- CHANGELOG.md | 5 ++ README.md | 2 +- fc-vault-rails.gemspec | 1 + lib/vault/rails/version.rb | 2 +- lib/vault/transit_json_codec.rb | 36 ++++++++++++ spec/unit/transit_json_codec_spec.rb | 88 ++++++++++++++++++++++++++++ 6 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 lib/vault/transit_json_codec.rb create mode 100644 spec/unit/transit_json_codec_spec.rb diff --git a/CHANGELOG.md b/CHANGELOG.md index 298dd33a..53ea2b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Vault Rails Changelog +## 2.1.1 (January 31, 2022) + +NEW FEATURES +- Added `TransitJsonCodec` class which encrypt and decrypt JSON values + ## 2.1.0 (January 11, 2022) Prevent db queries on boot -> so that db:create / assets:precompile work diff --git a/README.md b/README.md index 5c8e6a05..b1be0e39 100644 --- a/README.md +++ b/README.md @@ -364,5 +364,5 @@ We now have two versions of `Vault::EncryptedModel` a `Latest` version which tar Getting tests to run -------------------- ``` -$ bundle exec db:schema:load +$ bundle exec rake db:schema:load ``` diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index 95cca792..c98042eb 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -28,4 +28,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3.2" s.add_development_dependency "sqlite3", '~> 1.3' + s.add_development_dependency "oj" end diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 8ccd1eba..b1997af0 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.1.0" + VERSION = "2.1.1" def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') diff --git a/lib/vault/transit_json_codec.rb b/lib/vault/transit_json_codec.rb new file mode 100644 index 00000000..5c32bbb7 --- /dev/null +++ b/lib/vault/transit_json_codec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +require 'oj' + +module Vault + class TransitJsonCodec + def initialize(key) + @key = key + end + + def encrypt(plaintext) + return if plaintext.blank? + + secret = Vault.logical.write( + "transit/encrypt/#{key}", + plaintext: Base64.strict_encode64(Oj.dump(plaintext)) + ) + + secret.data[:ciphertext] + end + + def decrypt(ciphertext) + return if ciphertext.blank? + + secret = Vault.logical.write( + "transit/decrypt/#{key}", + ciphertext: ciphertext + ) + + Oj.load(Base64.strict_decode64(secret.data[:plaintext])) + end + + private + + attr_reader :key + end +end diff --git a/spec/unit/transit_json_codec_spec.rb b/spec/unit/transit_json_codec_spec.rb new file mode 100644 index 00000000..a1e6073f --- /dev/null +++ b/spec/unit/transit_json_codec_spec.rb @@ -0,0 +1,88 @@ +# frozen_string_literal: true + +require 'spec_helper' +require './lib/vault/transit_json_codec.rb' + +RSpec.describe Vault::TransitJsonCodec do + + subject(:codec) { described_class.new(encryption_key) } + let(:encryption_key) { 'blobbykey' } + + before do + Vault.configure { |c| c.address = Vault.client.address } + end + + describe '#encrypt' do + context 'when plaintext is blank' do + let(:plaintext) { nil } + + it 'returns nil' do + expect(codec.encrypt(plaintext)).to be_nil + end + end + + context 'when plaintext is empty' do + let(:plaintext) { '' } + + it 'returns nil' do + expect(codec.encrypt(plaintext)).to be_nil + end + end + + context 'when plaintext is present' do + let(:plaintext) { 'blobby' } + + context 'when encryption fails' do + before do + allow(Vault).to receive(:logical).and_raise(StandardError, 'Oh no!') + end + + it 're-raises error' do + expect { codec.encrypt(plaintext) }.to raise_error(StandardError) + end + end + + it 'encrypts the field' do + expect(codec.encrypt(plaintext)).to start_with('vault:v1:') + end + end + end + + describe '#decrypt' do + + context 'when ciphertext is nil' do + let(:ciphertext) { nil } + + it 'returns nil' do + expect(codec.decrypt(ciphertext)).to be_nil + end + end + + context 'when ciphertext is empty' do + let(:ciphertext) { '' } + + it 'returns nil' do + expect(codec.decrypt(ciphertext)).to be_nil + end + end + + context 'when ciphertext is present' do + let(:plaintext) { 'blobby' } + + context 'when decoding fails' do + before do + allow(Vault).to receive(:logical).and_raise(StandardError, 'Oh no!') + end + + it 're-raises error' do + expect { codec.decrypt(plaintext) }.to raise_error(StandardError) + end + end + + it 'decrypts an encrypted field' do + encrypted_text = codec.encrypt(plaintext) + expect(codec.decrypt(encrypted_text)).to eq(plaintext) + end + end + end +end From 60327f83929e9c59c209f4327451331155f1a867 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Mon, 11 Sep 2023 15:54:18 +0100 Subject: [PATCH 100/143] Added rails 7 --- .circleci/config.yml | 235 --------------------------------------- .drone.yml | 90 +++++++++++++++ Appraisals | 5 + CHANGELOG.md | 5 + fc-vault-rails.gemspec | 6 +- gemfiles/.bundle/config | 1 + gemfiles/rails_7.gemfile | 8 ++ 7 files changed, 112 insertions(+), 238 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .drone.yml create mode 100644 gemfiles/rails_7.gemfile diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 85f67ef3..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,235 +0,0 @@ -version: 2 - -aliases: - environment: &environment - RACK_ENV: test - VAULT_VERSION: 0.10.4 - command: &ruby-command | - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 - bundle exec rake app:db:create - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake - cache: - - restore_cache: &restore_cache - keys: - - vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum "Appraisals" }} - - save_cache: &save_cache - key: vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum "Appraisals" }} - paths: - - vendor/bundle - runs: - - run: &install-vault - name: Install Vault - command: | - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip - unzip vault.zip - sudo mv vault /usr/local/bin/ - - run: &rails4 - shell: /bin/bash -l - name: Rails 4.2 - environment: - BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile - command: *ruby-command - - run: &rails5 - shell: /bin/bash -l - name: Rails 5.0 - environment: - BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile - command: *ruby-command - - run: &rails51 - shell: /bin/bash -l - name: Rails 5.1 - environment: - BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - command: *ruby-command - - run: &rails52 - shell: /bin/bash -l - name: Rails 5.2 - environment: - BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - command: *ruby-command - - run: &rails6 - shell: /bin/bash -l - name: Rails 6 - environment: - BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - command: *ruby-command - - -jobs: - test_ruby_2_4: - docker: - - image: circleci/ruby:2.4 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails4 - - run: *rails5 - - run: *rails51 - - run: *rails52 - - test_ruby_2_5: - docker: - - image: circleci/ruby:2.5 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails4 - - run: *rails5 - - run: *rails51 - - run: *rails52 - - run: *rails6 - - test_ruby_2_6: - docker: - - image: circleci/ruby:2.6 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails5 - - run: *rails51 - - run: *rails52 - - run: *rails6 - - test_ruby_2_7: - docker: - - image: circleci/ruby:2.7 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails5 - - run: *rails51 - - run: *rails52 - - run: *rails6 - - publish-pre-release: - docker: - - image: circleci/ruby:2.7 - environment: - <<: *environment - - steps: - - checkout - - - run: - name: Login to JFrog - command: | - mkdir -p ~/.gem - curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials - chmod 600 ~/.gem/credentials - - - run: - name: Install Gem Versioner - shell: /bin/bash -l - command: gem install gem-versioner --version '~> 1.0' --no-document - - - run: - name: Build gem - shell: /bin/bash -l - command: | - PRE_RELEASE="$CIRCLE_BRANCH" gem build fc-vault-rails.gemspec - - - run: - name: Publish gem - shell: /bin/bash -l - command: | - package=$(ls -t1 fc-vault-rails-*.gem | head -1) - gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases - - publish-release: - docker: - - image: circleci/ruby:2.7 - environment: - <<: *environment - - steps: - - checkout - - - run: - name: Login to JFrog - command: | - mkdir -p ~/.gem - curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials - chmod 600 ~/.gem/credentials - - - run: - name: Install Gem Versioner - shell: /bin/bash -l - command: gem install gem-versioner --version '~> 1.0' --no-document - - - run: - name: Build gem - shell: /bin/bash -l - command: | - gem build fc-vault-rails.gemspec - - - run: - name: Publish gem - shell: /bin/bash -l - command: | - package=$(ls -t1 fc-vault-rails-*.gem | head -1) - gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local - -workflows: - version: 2 - - test-and-release: - jobs: - - test_ruby_2_4: - context: org-global - filters: - tags: - only: /.*/ - - - test_ruby_2_5: - context: org-global - filters: - tags: - only: /.*/ - - - test_ruby_2_6: - context: org-global - filters: - tags: - only: /.*/ - - - test_ruby_2_7: - context: org-global - filters: - tags: - only: /.*/ - - - publish-pre-release: - context: org-global - filters: - branches: - ignore: master - tags: - ignore: /.*/ - requires: - - test_ruby_2_4 - - test_ruby_2_5 - - test_ruby_2_6 - - test_ruby_2_7 - - - publish-release: - context: org-global - filters: - branches: - ignore: /.*/ - tags: - only: /^v[0-9]+(\.[0-9]+)*$/ - requires: - - test_ruby_2_4 - - test_ruby_2_5 - - test_ruby_2_6 - - test_ruby_2_7 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..bda7f0ff --- /dev/null +++ b/.drone.yml @@ -0,0 +1,90 @@ +--- +kind: pipeline +type: docker +name: build + +trigger: + event: + - push + +platform: + os: linux + arch: amd64 + +anchors: + artifactory_credentials: &artifactory_credentials + ARTIFACTORY_USER: + from_secret: artifactory_user + ARTIFACTORY_PASSWORD: + from_secret: artifactory_password + docker_credentials: &docker_credentials + DOCKER_USERNAME: + from_secret: docker_username + DOCKER_PASSWORD: + from_secret: docker_password + jfrog_credentials: &jfrog_credentials + BUNDLE_FUNDINGCIRCLE__JFROG__IO: + from_secret: bundle_fundingcircle__jfrog__io + test_commands: &test_commands + commands: + - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + - unzip vault.zip + - sudo mv vault /usr/local/bin/ + - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 + - bundle exec rake app:db:create + - bundle exec rake app:db:schema:load + - bundle exec rake app:db:test:prepare + - bundle exec rake + +concurrency: + limit: 1 + +steps: + - name: build_ruby4 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby5 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby51 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby52 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby6 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_6.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby7 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_7.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + \ No newline at end of file diff --git a/Appraisals b/Appraisals index 3d03c8ac..5772fb73 100644 --- a/Appraisals +++ b/Appraisals @@ -23,3 +23,8 @@ appraise 'rails-6' do gem 'rails', '~> 6' gem 'sqlite3', '~> 1.4' end + +appraise "rails-7" do + gem "rails", "~> 7" + gem 'sqlite3', '~> 1.4' +end diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ea2b13..3171a906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Vault Rails Changelog +## 2.1.2 (September 8, 2023) + +IMPROVEMENTS +- Add Rails 7 Support + ## 2.1.1 (January 31, 2022) NEW FEATURES diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index c98042eb..0dca6e24 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,16 +17,16 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", ">= 4.2" + s.add_dependency "activerecord" s.add_dependency "vault", "~> 0.7" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", ">= 4.2" + s.add_development_dependency "rails", "~> 6" s.add_development_dependency "byebug" s.add_development_dependency "pry" s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3.2" - s.add_development_dependency "sqlite3", '~> 1.3' + s.add_development_dependency "sqlite3", '~> 1.4' s.add_development_dependency "oj" end diff --git a/gemfiles/.bundle/config b/gemfiles/.bundle/config index c127f802..1d367dbc 100644 --- a/gemfiles/.bundle/config +++ b/gemfiles/.bundle/config @@ -1,2 +1,3 @@ --- BUNDLE_RETRY: "1" +BUNDLE_WITH: "development" diff --git a/gemfiles/rails_7.gemfile b/gemfiles/rails_7.gemfile new file mode 100644 index 00000000..1caf2486 --- /dev/null +++ b/gemfiles/rails_7.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 7" +gem "sqlite3", "~> 1.4" + +gemspec path: "../" From 6af8643df14bb7d8159f5c764c77d36c6e1d714e Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Mon, 11 Sep 2023 16:40:55 +0100 Subject: [PATCH 101/143] Adding codeowners --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..bda6f0ff --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @FundingCircle/team-customer-data \ No newline at end of file From b6b7b269a9566b5aeb171b99d19f907785695440 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 09:35:58 +0100 Subject: [PATCH 102/143] Remove CircleCI and add drone --- .circleci/config.yml | 235 ------------------------------------------- .drone.yml | 81 +++++++++++++++ 2 files changed, 81 insertions(+), 235 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .drone.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 85f67ef3..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,235 +0,0 @@ -version: 2 - -aliases: - environment: &environment - RACK_ENV: test - VAULT_VERSION: 0.10.4 - command: &ruby-command | - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 - bundle exec rake app:db:create - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake - cache: - - restore_cache: &restore_cache - keys: - - vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum "Appraisals" }} - - save_cache: &save_cache - key: vault-rails-{{checksum "fc-vault-rails.gemspec" }}-{{checksum "Appraisals" }} - paths: - - vendor/bundle - runs: - - run: &install-vault - name: Install Vault - command: | - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip - unzip vault.zip - sudo mv vault /usr/local/bin/ - - run: &rails4 - shell: /bin/bash -l - name: Rails 4.2 - environment: - BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile - command: *ruby-command - - run: &rails5 - shell: /bin/bash -l - name: Rails 5.0 - environment: - BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile - command: *ruby-command - - run: &rails51 - shell: /bin/bash -l - name: Rails 5.1 - environment: - BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - command: *ruby-command - - run: &rails52 - shell: /bin/bash -l - name: Rails 5.2 - environment: - BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - command: *ruby-command - - run: &rails6 - shell: /bin/bash -l - name: Rails 6 - environment: - BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - command: *ruby-command - - -jobs: - test_ruby_2_4: - docker: - - image: circleci/ruby:2.4 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails4 - - run: *rails5 - - run: *rails51 - - run: *rails52 - - test_ruby_2_5: - docker: - - image: circleci/ruby:2.5 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails4 - - run: *rails5 - - run: *rails51 - - run: *rails52 - - run: *rails6 - - test_ruby_2_6: - docker: - - image: circleci/ruby:2.6 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails5 - - run: *rails51 - - run: *rails52 - - run: *rails6 - - test_ruby_2_7: - docker: - - image: circleci/ruby:2.7 - environment: - <<: *environment - steps: - - checkout - - run: *install-vault - - run: *rails5 - - run: *rails51 - - run: *rails52 - - run: *rails6 - - publish-pre-release: - docker: - - image: circleci/ruby:2.7 - environment: - <<: *environment - - steps: - - checkout - - - run: - name: Login to JFrog - command: | - mkdir -p ~/.gem - curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials - chmod 600 ~/.gem/credentials - - - run: - name: Install Gem Versioner - shell: /bin/bash -l - command: gem install gem-versioner --version '~> 1.0' --no-document - - - run: - name: Build gem - shell: /bin/bash -l - command: | - PRE_RELEASE="$CIRCLE_BRANCH" gem build fc-vault-rails.gemspec - - - run: - name: Publish gem - shell: /bin/bash -l - command: | - package=$(ls -t1 fc-vault-rails-*.gem | head -1) - gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases - - publish-release: - docker: - - image: circleci/ruby:2.7 - environment: - <<: *environment - - steps: - - checkout - - - run: - name: Login to JFrog - command: | - mkdir -p ~/.gem - curl --user "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems/api/v1/api_key.yaml > ~/.gem/credentials - chmod 600 ~/.gem/credentials - - - run: - name: Install Gem Versioner - shell: /bin/bash -l - command: gem install gem-versioner --version '~> 1.0' --no-document - - - run: - name: Build gem - shell: /bin/bash -l - command: | - gem build fc-vault-rails.gemspec - - - run: - name: Publish gem - shell: /bin/bash -l - command: | - package=$(ls -t1 fc-vault-rails-*.gem | head -1) - gem push "$package" --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local - -workflows: - version: 2 - - test-and-release: - jobs: - - test_ruby_2_4: - context: org-global - filters: - tags: - only: /.*/ - - - test_ruby_2_5: - context: org-global - filters: - tags: - only: /.*/ - - - test_ruby_2_6: - context: org-global - filters: - tags: - only: /.*/ - - - test_ruby_2_7: - context: org-global - filters: - tags: - only: /.*/ - - - publish-pre-release: - context: org-global - filters: - branches: - ignore: master - tags: - ignore: /.*/ - requires: - - test_ruby_2_4 - - test_ruby_2_5 - - test_ruby_2_6 - - test_ruby_2_7 - - - publish-release: - context: org-global - filters: - branches: - ignore: /.*/ - tags: - only: /^v[0-9]+(\.[0-9]+)*$/ - requires: - - test_ruby_2_4 - - test_ruby_2_5 - - test_ruby_2_6 - - test_ruby_2_7 diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 00000000..25afa4d9 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,81 @@ +--- +kind: pipeline +type: docker +name: build + +trigger: + event: + - push + +platform: + os: linux + arch: amd64 + +anchors: + artifactory_credentials: &artifactory_credentials + ARTIFACTORY_USER: + from_secret: artifactory_user + ARTIFACTORY_PASSWORD: + from_secret: artifactory_password + docker_credentials: &docker_credentials + DOCKER_USERNAME: + from_secret: docker_username + DOCKER_PASSWORD: + from_secret: docker_password + jfrog_credentials: &jfrog_credentials + BUNDLE_FUNDINGCIRCLE__JFROG__IO: + from_secret: bundle_fundingcircle__jfrog__io + test_commands: &test_commands + commands: + - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + - unzip vault.zip + - sudo mv vault /usr/local/bin/ + - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 + - bundle exec rake app:db:create + - bundle exec rake app:db:schema:load + - bundle exec rake app:db:test:prepare + - bundle exec rake + +concurrency: + limit: 1 + +steps: + - name: build_ruby4 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby5 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby51 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby52 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands + + - name: build_ruby6 + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + BUNDLE_GEMFILE: gemfiles/rails_6.gemfile + <<: *docker_credentials + <<: *jfrog_credentials + <<: *test_commands \ No newline at end of file From a3e44344df9eb9f20a70d89367f0e078411c41df Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 11:26:36 +0100 Subject: [PATCH 103/143] Forcing drone to rebuild From 5c8158a29e5b4df9d89384ae5b7d76a2eb5e493d Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 16:50:26 +0100 Subject: [PATCH 104/143] Added vault version --- .drone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.drone.yml b/.drone.yml index 25afa4d9..1c154bc9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,6 +25,9 @@ anchors: jfrog_credentials: &jfrog_credentials BUNDLE_FUNDINGCIRCLE__JFROG__IO: from_secret: bundle_fundingcircle__jfrog__io + vault_version: &vault_version + VAULT_VERSION: + from_secret: VAULT_VERSION test_commands: &test_commands commands: - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip From c7127d917655a85d00110dd0d291a43bb7a84af2 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 16:53:50 +0100 Subject: [PATCH 105/143] Added into the steps --- .drone.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.drone.yml b/.drone.yml index 1c154bc9..bb302436 100644 --- a/.drone.yml +++ b/.drone.yml @@ -49,6 +49,7 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile <<: *docker_credentials <<: *jfrog_credentials + <<: *vault_version <<: *test_commands - name: build_ruby5 @@ -57,6 +58,7 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile <<: *docker_credentials <<: *jfrog_credentials + <<: *vault_version <<: *test_commands - name: build_ruby51 @@ -65,6 +67,7 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile <<: *docker_credentials <<: *jfrog_credentials + <<: *vault_version <<: *test_commands - name: build_ruby52 @@ -73,6 +76,7 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile <<: *docker_credentials <<: *jfrog_credentials + <<: *vault_version <<: *test_commands - name: build_ruby6 @@ -81,4 +85,5 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile <<: *docker_credentials <<: *jfrog_credentials + <<: *vault_version <<: *test_commands \ No newline at end of file From 346da6b2be6767735d5dd37118730b353bef521b Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 16:56:29 +0100 Subject: [PATCH 106/143] Change name to secret --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index bb302436..a41e7e15 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,7 +27,7 @@ anchors: from_secret: bundle_fundingcircle__jfrog__io vault_version: &vault_version VAULT_VERSION: - from_secret: VAULT_VERSION + from_secret: vault_version test_commands: &test_commands commands: - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip From c369dc813fe7331ab06a869c62c3ab8e6f37d091 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 17:18:15 +0100 Subject: [PATCH 107/143] Force to use 1.1.3 --- .drone.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index a41e7e15..bfe1d037 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,8 +26,7 @@ anchors: BUNDLE_FUNDINGCIRCLE__JFROG__IO: from_secret: bundle_fundingcircle__jfrog__io vault_version: &vault_version - VAULT_VERSION: - from_secret: vault_version + VAULT_VERSION: 1.1.3 test_commands: &test_commands commands: - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip From ff30a999df0686c9836bd1683791799f260a2d63 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 17:26:40 +0100 Subject: [PATCH 108/143] get the environment --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index bfe1d037..b255c95a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -29,7 +29,7 @@ anchors: VAULT_VERSION: 1.1.3 test_commands: &test_commands commands: - - wget -O vault.zip -q https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + - wget -O vault.zip -q https://releases.hashicorp.com/vault/$VAULT_VERSION/vault_$VAULT_VERSION_linux_amd64.zip - unzip vault.zip - sudo mv vault /usr/local/bin/ - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 From 71422785693ec5cf7e7e4212b5473743ebbae344 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 17:30:19 +0100 Subject: [PATCH 109/143] Force version --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index b255c95a..7c89f4df 100644 --- a/.drone.yml +++ b/.drone.yml @@ -29,7 +29,7 @@ anchors: VAULT_VERSION: 1.1.3 test_commands: &test_commands commands: - - wget -O vault.zip -q https://releases.hashicorp.com/vault/$VAULT_VERSION/vault_$VAULT_VERSION_linux_amd64.zip + - wget -O vault.zip -q https://releases.hashicorp.com/vault/1.1.3/vault_1.1.3_linux_amd64.zip - unzip vault.zip - sudo mv vault /usr/local/bin/ - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 From 3182e20924dfbdc45322fe7b65fa987ac9f6f92c Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Tue, 12 Sep 2023 17:31:44 +0100 Subject: [PATCH 110/143] Remove sudo --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 7c89f4df..3e990273 100644 --- a/.drone.yml +++ b/.drone.yml @@ -31,7 +31,7 @@ anchors: commands: - wget -O vault.zip -q https://releases.hashicorp.com/vault/1.1.3/vault_1.1.3_linux_amd64.zip - unzip vault.zip - - sudo mv vault /usr/local/bin/ + - mv vault /usr/local/bin/ - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 - bundle exec rake app:db:create - bundle exec rake app:db:schema:load From fdd809abd29d201f6e5550b94ca5538daa87d04f Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 13:11:38 +0100 Subject: [PATCH 111/143] Refactor yaml --- .drone.yml | 67 +++++++++++++++++++++--------------------------------- 1 file changed, 26 insertions(+), 41 deletions(-) diff --git a/.drone.yml b/.drone.yml index 3e990273..367d1690 100644 --- a/.drone.yml +++ b/.drone.yml @@ -17,72 +17,57 @@ anchors: from_secret: artifactory_user ARTIFACTORY_PASSWORD: from_secret: artifactory_password - docker_credentials: &docker_credentials - DOCKER_USERNAME: - from_secret: docker_username - DOCKER_PASSWORD: - from_secret: docker_password - jfrog_credentials: &jfrog_credentials - BUNDLE_FUNDINGCIRCLE__JFROG__IO: - from_secret: bundle_fundingcircle__jfrog__io - vault_version: &vault_version - VAULT_VERSION: 1.1.3 - test_commands: &test_commands - commands: - - wget -O vault.zip -q https://releases.hashicorp.com/vault/1.1.3/vault_1.1.3_linux_amd64.zip - - unzip vault.zip - - mv vault /usr/local/bin/ - - bundle check --path=vendor/bundle || bundle install --binstubs --jobs 4 --path=vendor/bundle --retry 3 - - bundle exec rake app:db:create - - bundle exec rake app:db:schema:load - - bundle exec rake app:db:test:prepare - - bundle exec rake + vault_tag: &vault_tag + VAULT_VERSION: + from_secret: vault_version + commands: &commands + - apk add --no-cache curl + - curl -sSL -o /usr/local/bin/vault.zip https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + - unzip /usr/local/bin/vault.zip -d /usr/local/bin + - chmod +x /usr/local/bin/vault + - apk add --no-cache build-base sqlite-dev + - bundle install --jobs=4 --retry=3 + - bundle exec rake app:db:create + - bundle exec rake app:db:schema:load + - bundle exec rake app:db:test:prepare + - bundle exec rake + concurrency: limit: 1 steps: - name: build_ruby4 - image: quay.io/fundingcircle/alpine-ruby-builder:latest + image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile - <<: *docker_credentials - <<: *jfrog_credentials - <<: *vault_version - <<: *test_commands + <<: [*vault_tag, *artifactory_credentials] + commands: *commands - name: build_ruby5 image: quay.io/fundingcircle/alpine-ruby-builder:latest environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile - <<: *docker_credentials - <<: *jfrog_credentials - <<: *vault_version - <<: *test_commands + <<: [*vault_tag, *artifactory_credentials] + commands: *commands - name: build_ruby51 image: quay.io/fundingcircle/alpine-ruby-builder:latest environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - <<: *docker_credentials - <<: *jfrog_credentials - <<: *vault_version - <<: *test_commands + <<: [*vault_tag, *artifactory_credentials] + commands: *commands - name: build_ruby52 image: quay.io/fundingcircle/alpine-ruby-builder:latest environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - <<: *docker_credentials - <<: *jfrog_credentials - <<: *vault_version - <<: *test_commands + <<: [*vault_tag, *artifactory_credentials] + commands: *commands - name: build_ruby6 image: quay.io/fundingcircle/alpine-ruby-builder:latest environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - <<: *docker_credentials - <<: *jfrog_credentials - <<: *vault_version - <<: *test_commands \ No newline at end of file + <<: [*vault_tag, *artifactory_credentials] + commands: *commands \ No newline at end of file From f9bc5d6824208198f0d6d8ba4d8351adc8be37af Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 13:36:58 +0100 Subject: [PATCH 112/143] Debugging vault --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index 367d1690..a254eb8a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,6 +22,7 @@ anchors: from_secret: vault_version commands: &commands - apk add --no-cache curl + - echo "Installing Vault ${VAULT_VERSION}" - curl -sSL -o /usr/local/bin/vault.zip https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip - unzip /usr/local/bin/vault.zip -d /usr/local/bin - chmod +x /usr/local/bin/vault From 959e061cbe4b8d43ddaf9da1409206e1bdfac59a Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 13:38:50 +0100 Subject: [PATCH 113/143] Try without params --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index a254eb8a..10dcc4a5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,7 +22,7 @@ anchors: from_secret: vault_version commands: &commands - apk add --no-cache curl - - echo "Installing Vault ${VAULT_VERSION}" + - echo "Installing Vault" $VAULT_VERSION - curl -sSL -o /usr/local/bin/vault.zip https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip - unzip /usr/local/bin/vault.zip -d /usr/local/bin - chmod +x /usr/local/bin/vault From 8887e408fae977e525139cf08067dd4167dcbfed Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 13:41:47 +0100 Subject: [PATCH 114/143] Maybe it populate it --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 10dcc4a5..ff54b93c 100644 --- a/.drone.yml +++ b/.drone.yml @@ -23,7 +23,7 @@ anchors: commands: &commands - apk add --no-cache curl - echo "Installing Vault" $VAULT_VERSION - - curl -sSL -o /usr/local/bin/vault.zip https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_amd64.zip + - curl -sSL -o /usr/local/bin/vault.zip https://releases.hashicorp.com/vault/$(echo $VAULT_VERSION)/vault_$(echo $VAULT_VERSION)_linux_amd64.zip - unzip /usr/local/bin/vault.zip -d /usr/local/bin - chmod +x /usr/local/bin/vault - apk add --no-cache build-base sqlite-dev From e8db77a3a6143af3ca7a9785994078da42e7ce10 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 13:49:53 +0100 Subject: [PATCH 115/143] build with environment production and ruby 3 --- .drone.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index ff54b93c..2bfbf926 100644 --- a/.drone.yml +++ b/.drone.yml @@ -46,29 +46,33 @@ steps: commands: *commands - name: build_ruby5 - image: quay.io/fundingcircle/alpine-ruby-builder:latest + image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile + RAILS_ENV: production <<: [*vault_tag, *artifactory_credentials] commands: *commands - name: build_ruby51 - image: quay.io/fundingcircle/alpine-ruby-builder:latest + image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile + RAILS_ENV: production <<: [*vault_tag, *artifactory_credentials] commands: *commands - name: build_ruby52 - image: quay.io/fundingcircle/alpine-ruby-builder:latest + image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile + RAILS_ENV: production <<: [*vault_tag, *artifactory_credentials] commands: *commands - name: build_ruby6 - image: quay.io/fundingcircle/alpine-ruby-builder:latest + image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile + RAILS_ENV: production <<: [*vault_tag, *artifactory_credentials] commands: *commands \ No newline at end of file From 7ede94fab4868698a2488229b0c709820606e6c0 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 15:00:59 +0100 Subject: [PATCH 116/143] Move to 2.6 ruby version due to incompatibility between sqlite and ruby 3 --- .drone.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 2bfbf926..d73f30fe 100644 --- a/.drone.yml +++ b/.drone.yml @@ -46,7 +46,7 @@ steps: commands: *commands - name: build_ruby5 - image: quay.io/fundingcircle/alpine-ruby-builder:3.0 + image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile RAILS_ENV: production @@ -54,7 +54,7 @@ steps: commands: *commands - name: build_ruby51 - image: quay.io/fundingcircle/alpine-ruby-builder:3.0 + image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile RAILS_ENV: production @@ -62,7 +62,7 @@ steps: commands: *commands - name: build_ruby52 - image: quay.io/fundingcircle/alpine-ruby-builder:3.0 + image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile RAILS_ENV: production From 03b7d7134ef2aa6f2ca86f814463a6b3b6fde89a Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 15:07:42 +0100 Subject: [PATCH 117/143] RAILS_env to default --- .drone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index d73f30fe..5c8e53ce 100644 --- a/.drone.yml +++ b/.drone.yml @@ -49,7 +49,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile - RAILS_ENV: production + RAILS_ENV: default <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -57,7 +57,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - RAILS_ENV: production + RAILS_ENV: default <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -65,7 +65,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - RAILS_ENV: production + RAILS_ENV: default <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -73,6 +73,6 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - RAILS_ENV: production + RAILS_ENV: default <<: [*vault_tag, *artifactory_credentials] commands: *commands \ No newline at end of file From cb2a5e51b87a20ea858cef213244939c22869261 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 15:35:21 +0100 Subject: [PATCH 118/143] Use development --- .drone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5c8e53ce..9e4abe5b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -49,7 +49,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile - RAILS_ENV: default + RAILS_ENV: development <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -57,7 +57,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - RAILS_ENV: default + RAILS_ENV: development <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -65,7 +65,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - RAILS_ENV: default + RAILS_ENV: development <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -73,6 +73,6 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - RAILS_ENV: default + RAILS_ENV: development <<: [*vault_tag, *artifactory_credentials] commands: *commands \ No newline at end of file From 17f66d0f06af258f097bd783fd4bb540edf24eb8 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 15:46:46 +0100 Subject: [PATCH 119/143] Modify step 5 --- .drone.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 9e4abe5b..7849eb42 100644 --- a/.drone.yml +++ b/.drone.yml @@ -51,7 +51,9 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile RAILS_ENV: development <<: [*vault_tag, *artifactory_credentials] - commands: *commands + commands: + - export RAILS_ENV=development + - *commands - name: build_ruby51 image: quay.io/fundingcircle/alpine-ruby-builder:2.6 From 7d870708b0dab77d55b5fa7257b2b770fdb95211 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 15:49:05 +0100 Subject: [PATCH 120/143] Back to normal --- .drone.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 7849eb42..9e4abe5b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -51,9 +51,7 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile RAILS_ENV: development <<: [*vault_tag, *artifactory_credentials] - commands: - - export RAILS_ENV=development - - *commands + commands: *commands - name: build_ruby51 image: quay.io/fundingcircle/alpine-ruby-builder:2.6 From 124ba0446f770c0d7df708a47860c28797261700 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 16:25:13 +0100 Subject: [PATCH 121/143] add set environment --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index 9e4abe5b..6fd1256d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -29,6 +29,7 @@ anchors: - apk add --no-cache build-base sqlite-dev - bundle install --jobs=4 --retry=3 - bundle exec rake app:db:create + - bin/rails db:environment:set RAILS_ENV=development - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake From 789da75c4e3c7a03bbf0810b27f7b5bb2375cd53 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 16:43:15 +0100 Subject: [PATCH 122/143] forcing to have the development --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 6fd1256d..02db61a0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -28,8 +28,8 @@ anchors: - chmod +x /usr/local/bin/vault - apk add --no-cache build-base sqlite-dev - bundle install --jobs=4 --retry=3 + - export RAILS_ENV=development - bundle exec rake app:db:create - - bin/rails db:environment:set RAILS_ENV=development - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake From 9bf5e0a2d2a22d108e1ad62ebc5a5bf725706d84 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Thu, 14 Sep 2023 16:51:04 +0100 Subject: [PATCH 123/143] Trying to move environment variable --- .drone.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 02db61a0..5e1d9615 100644 --- a/.drone.yml +++ b/.drone.yml @@ -22,14 +22,14 @@ anchors: from_secret: vault_version commands: &commands - apk add --no-cache curl - - echo "Installing Vault" $VAULT_VERSION - curl -sSL -o /usr/local/bin/vault.zip https://releases.hashicorp.com/vault/$(echo $VAULT_VERSION)/vault_$(echo $VAULT_VERSION)_linux_amd64.zip - unzip /usr/local/bin/vault.zip -d /usr/local/bin - chmod +x /usr/local/bin/vault - - apk add --no-cache build-base sqlite-dev + - apk add --no-cache build-base sqlite-dev + - export RAILS_ENV=development - bundle install --jobs=4 --retry=3 - - export RAILS_ENV=development - bundle exec rake app:db:create + - echo $RAILS_ENV - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake @@ -49,8 +49,8 @@ steps: - name: build_ruby5 image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: - BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile RAILS_ENV: development + BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile <<: [*vault_tag, *artifactory_credentials] commands: *commands From d49eaffc6ba1f2df8921770efa31d216fd94a75b Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 09:31:25 +0100 Subject: [PATCH 124/143] Use test instead --- .drone.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5e1d9615..67858893 100644 --- a/.drone.yml +++ b/.drone.yml @@ -26,7 +26,6 @@ anchors: - unzip /usr/local/bin/vault.zip -d /usr/local/bin - chmod +x /usr/local/bin/vault - apk add --no-cache build-base sqlite-dev - - export RAILS_ENV=development - bundle install --jobs=4 --retry=3 - bundle exec rake app:db:create - echo $RAILS_ENV @@ -49,7 +48,7 @@ steps: - name: build_ruby5 image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: - RAILS_ENV: development + RAILS_ENV: test BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -58,7 +57,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - RAILS_ENV: development + RAILS_ENV: test <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -66,7 +65,7 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - RAILS_ENV: development + RAILS_ENV: test <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -74,6 +73,6 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - RAILS_ENV: development + RAILS_ENV: test <<: [*vault_tag, *artifactory_credentials] commands: *commands \ No newline at end of file From 1961bf00d0c7d1b984a58290b29b271f8ea3da45 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 09:55:41 +0100 Subject: [PATCH 125/143] Force environment --- .drone.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 67858893..693381cb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,8 +27,7 @@ anchors: - chmod +x /usr/local/bin/vault - apk add --no-cache build-base sqlite-dev - bundle install --jobs=4 --retry=3 - - bundle exec rake app:db:create - - echo $RAILS_ENV + - bundle exec rake app:db:create RAILS_ENV=$RAILS_ENV - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake From 2f9e90a69a6fee50e223fc51b073c7d765c259c9 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 10:18:11 +0100 Subject: [PATCH 126/143] Disable database check --- .drone.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 693381cb..915b758a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -27,7 +27,7 @@ anchors: - chmod +x /usr/local/bin/vault - apk add --no-cache build-base sqlite-dev - bundle install --jobs=4 --retry=3 - - bundle exec rake app:db:create RAILS_ENV=$RAILS_ENV + - bundle exec rake app:db:create - bundle exec rake app:db:schema:load - bundle exec rake app:db:test:prepare - bundle exec rake @@ -49,6 +49,7 @@ steps: environment: RAILS_ENV: test BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile + DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -57,6 +58,7 @@ steps: environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile RAILS_ENV: test + DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -65,6 +67,7 @@ steps: environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile RAILS_ENV: test + DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -73,5 +76,6 @@ steps: environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile RAILS_ENV: test + DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands \ No newline at end of file From d182fec7cc76ccd0872b59cbf7e7cd079a4ac35d Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 11:22:02 +0100 Subject: [PATCH 127/143] Adding publishing steps --- .drone.yml | 117 ++++++++++++++++++++++++++++++++++++++---- bin/check_gem_version | 14 +++++ 2 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 bin/check_gem_version diff --git a/.drone.yml b/.drone.yml index 915b758a..5c2d62d3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -37,15 +37,15 @@ concurrency: limit: 1 steps: - - name: build_ruby4 + - name: build_rails4 image: quay.io/fundingcircle/alpine-ruby-builder:2.6 environment: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile <<: [*vault_tag, *artifactory_credentials] commands: *commands - - name: build_ruby5 - image: quay.io/fundingcircle/alpine-ruby-builder:2.6 + - name: build_rails5 + image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: RAILS_ENV: test BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile @@ -53,8 +53,8 @@ steps: <<: [*vault_tag, *artifactory_credentials] commands: *commands - - name: build_ruby51 - image: quay.io/fundingcircle/alpine-ruby-builder:2.6 + - name: build_rails51 + image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile RAILS_ENV: test @@ -62,8 +62,8 @@ steps: <<: [*vault_tag, *artifactory_credentials] commands: *commands - - name: build_ruby52 - image: quay.io/fundingcircle/alpine-ruby-builder:2.6 + - name: build_rails52 + image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile RAILS_ENV: test @@ -71,11 +71,110 @@ steps: <<: [*vault_tag, *artifactory_credentials] commands: *commands - - name: build_ruby6 + - name: build_rails6 image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile RAILS_ENV: test DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] - commands: *commands \ No newline at end of file + commands: *commands + + - name: publish_feature_branch_gem + image: quay.io/fundingcircle/alpine-ruby-builder:2.7 + environment: + <<: *artifactory_credentials + GEM_REPOSITORY: 'rubygems-pre-releases' + commands: + - gem install gem-versioner + - PRE_RELEASE=$(git rev-parse --short HEAD) gem build fc-vault-rails.gemspec + - | + mkdir -p ~/.gem + curl -u "$ARTIFACTORY_USER":"$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases/api/v1/api_key.yaml > ~/.gem/credentials + chmod 600 ~/.gem/credentials + - | + package=$(ls -t1 active-codas*.gem | head -1) + gem push $package --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases + depends_on: + - build_rails4 + - build_rails5 + - build_rails51 + - build_rails52 + - build_rails6 + when: + branch: + exclude: + - master + + +--- +kind: pipeline +type: docker +name: deploy + +trigger: + event: + - promote + when: + branch: + - master + +anchors: + artifactory_credentials: &artifactory_credentials + ARTIFACTORY_USER: + from_secret: artifactory_user + ARTIFACTORY_PASSWORD: + from_secret: artifactory_password + +# Platform for job, always Linux amd64 +platform: + os: linux + arch: amd64 + +steps: + - name: check_gem_version + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + <<: *artifactory_credentials + commands: + - bin/check_gem_version + depends_on: + - clone + + - name: publish_master_gem + image: quay.io/fundingcircle/alpine-ruby-builder:2.7 + environment: + <<: *artifactory_credentials + GEM_REPOSITORY: 'rubygems-local' + commands: + - gem build fc-vault-rails.gemspec + - | + mkdir -p ~/.gem + curl -u "$ARTIFACTORY_USER":"$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local/api/v1/api_key.yaml > ~/.gem/credentials + chmod 600 ~/.gem/credentials + - | + package=$(ls -t1 fc-vault-rails*.gem | head -1) + gem push $package --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local + depends_on: + - check_gem_version + + - name: tag_git_repo + image: quay.io/fundingcircle/alpine-ruby-builder:latest + environment: + GITHUB_TOKEN: + from_secret: github_token + commands: + - | + current_version="$(ruby -e 'require "./lib/vault/rails/version.rb";puts Vault::Rails::VERSION')"; + git tag v$current_version -m v$current_version; + git push --tags; + depends_on: + - publish_master_gem + + - name: status + image: quay.io/fundingcircle/drone-github-status:latest + settings: + env: production + locale: UK + depends_on: + - publish_master_gem \ No newline at end of file diff --git a/bin/check_gem_version b/bin/check_gem_version new file mode 100644 index 00000000..74efbd3d --- /dev/null +++ b/bin/check_gem_version @@ -0,0 +1,14 @@ +echo -n "Checking existing available versions of gem... "; +current_version="$(ruby -e 'require "./lib/vault/rails/version.rb";puts Vault::Rails::VERSION')"; +version_info_json=$(curl -s -u "$ARTIFACTORY_USER":"$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local/api/v1/versions/active-codas.json); + +if echo "$version_info_json" | jq -e '[.[] | select(.number|test("'$current_version'"))]|length==0' > /dev/null; then + echo -e '\e[32mOK\e[0m'; +else + echo -e '\e[31mFAIL\e[0m'; + echo 'Existing published versions:'; + echo "$version_info_json" | jq 'map(.number)'; + echo -e 'Your version: \e[31m'$current_version'\e[0m.'; + echo 'Please bump the version in `lib/active_codas/version.rb`.' + exit 1; +fi \ No newline at end of file From 5679ec4f656263c50b41eb917d1c8da8cbc17eac Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 11:28:33 +0100 Subject: [PATCH 128/143] remove rails env --- .drone.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5c2d62d3..b4a5af29 100644 --- a/.drone.yml +++ b/.drone.yml @@ -47,7 +47,6 @@ steps: - name: build_rails5 image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: - RAILS_ENV: test BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] @@ -57,7 +56,6 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile - RAILS_ENV: test DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -66,7 +64,6 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile - RAILS_ENV: test DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands @@ -75,7 +72,6 @@ steps: image: quay.io/fundingcircle/alpine-ruby-builder:3.0 environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile - RAILS_ENV: test DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands From 0d5f6af85292a2fcccf3ab92401d334a1868c9c3 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 11:38:01 +0100 Subject: [PATCH 129/143] Move to oficial ruby images --- .drone.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index b4a5af29..dcfb45c3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -38,14 +38,14 @@ concurrency: steps: - name: build_rails4 - image: quay.io/fundingcircle/alpine-ruby-builder:2.6 + image: ruby:2.6-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile <<: [*vault_tag, *artifactory_credentials] commands: *commands - name: build_rails5 - image: quay.io/fundingcircle/alpine-ruby-builder:2.7 + image: ruby:2.7-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 @@ -53,7 +53,7 @@ steps: commands: *commands - name: build_rails51 - image: quay.io/fundingcircle/alpine-ruby-builder:2.7 + image: ruby:2.7-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_5.1.gemfile DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 @@ -61,7 +61,7 @@ steps: commands: *commands - name: build_rails52 - image: quay.io/fundingcircle/alpine-ruby-builder:2.7 + image: ruby:2.7-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_5.2.gemfile DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 @@ -69,7 +69,7 @@ steps: commands: *commands - name: build_rails6 - image: quay.io/fundingcircle/alpine-ruby-builder:3.0 + image: ruby:3.0-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_6.gemfile DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 From 8d1a293f37a70d7ada5b95d79170384d79609580 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 13:59:54 +0100 Subject: [PATCH 130/143] Added tzinfo into the appraisal --- .drone.yml | 2 +- Appraisals | 4 ++++ gemfiles/rails_4.2.gemfile | 3 ++- gemfiles/rails_5.0.gemfile | 3 ++- gemfiles/rails_5.1.gemfile | 3 ++- gemfiles/rails_5.2.gemfile | 3 ++- gemfiles/rails_6.gemfile | 3 ++- 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.drone.yml b/.drone.yml index dcfb45c3..08c7d768 100644 --- a/.drone.yml +++ b/.drone.yml @@ -38,7 +38,7 @@ concurrency: steps: - name: build_rails4 - image: ruby:2.6-alpine + image: ruby:2.7-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile <<: [*vault_tag, *artifactory_credentials] diff --git a/Appraisals b/Appraisals index 3d03c8ac..1b8a0bbf 100644 --- a/Appraisals +++ b/Appraisals @@ -7,19 +7,23 @@ end appraise 'rails-5.0' do gem 'rails', '~> 5.0.0' gem 'sqlite3', '~> 1.3.13' + gem 'tzinfo-data' end appraise 'rails-5.1' do gem 'rails', '~> 5.1.0' gem 'sqlite3', '~> 1.3.13' + gem 'tzinfo-data' end appraise 'rails-5.2' do gem 'rails', '~> 5.2.0' gem 'sqlite3', '~> 1.3.13' + gem 'tzinfo-data' end appraise 'rails-6' do gem 'rails', '~> 6' gem 'sqlite3', '~> 1.4' + gem 'tzinfo-data' end diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 510b3205..97b8a0ee 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -4,6 +4,7 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" gem "sqlite3", "~> 1.3.13" +gem "tzinfo-data" gem "bundler", "~> 1.17.3" -gemspec path: "../" +gemspec path: "../../" diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile index 23d47f65..66607cd2 100644 --- a/gemfiles/rails_5.0.gemfile +++ b/gemfiles/rails_5.0.gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 5.0.0" gem "sqlite3", "~> 1.3.13" +gem "tzinfo-data" -gemspec path: "../" +gemspec path: "../../" diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile index 226e6174..ddd5575f 100644 --- a/gemfiles/rails_5.1.gemfile +++ b/gemfiles/rails_5.1.gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 5.1.0" gem "sqlite3", "~> 1.3.13" +gem "tzinfo-data" -gemspec path: "../" +gemspec path: "../../" diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile index 9c104738..5f75d71d 100644 --- a/gemfiles/rails_5.2.gemfile +++ b/gemfiles/rails_5.2.gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 5.2.0" gem "sqlite3", "~> 1.3.13" +gem "tzinfo-data" -gemspec path: "../" +gemspec path: "../../" diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile index e6b9af96..b2bdc3e1 100644 --- a/gemfiles/rails_6.gemfile +++ b/gemfiles/rails_6.gemfile @@ -4,5 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 6" gem "sqlite3", "~> 1.4" +gem "tzinfo-data" -gemspec path: "../" +gemspec path: "../../" From 571e346bb28d11762972f4d6b8f17a977247dbdb Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 14:11:27 +0100 Subject: [PATCH 131/143] regenerate the gemfiles --- gemfiles/rails_4.2.gemfile | 3 +-- gemfiles/rails_5.0.gemfile | 2 +- gemfiles/rails_5.1.gemfile | 2 +- gemfiles/rails_5.2.gemfile | 2 +- gemfiles/rails_6.gemfile | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 97b8a0ee..510b3205 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -4,7 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" gem "sqlite3", "~> 1.3.13" -gem "tzinfo-data" gem "bundler", "~> 1.17.3" -gemspec path: "../../" +gemspec path: "../" diff --git a/gemfiles/rails_5.0.gemfile b/gemfiles/rails_5.0.gemfile index 66607cd2..997d7223 100644 --- a/gemfiles/rails_5.0.gemfile +++ b/gemfiles/rails_5.0.gemfile @@ -6,4 +6,4 @@ gem "rails", "~> 5.0.0" gem "sqlite3", "~> 1.3.13" gem "tzinfo-data" -gemspec path: "../../" +gemspec path: "../" diff --git a/gemfiles/rails_5.1.gemfile b/gemfiles/rails_5.1.gemfile index ddd5575f..8fe3549b 100644 --- a/gemfiles/rails_5.1.gemfile +++ b/gemfiles/rails_5.1.gemfile @@ -6,4 +6,4 @@ gem "rails", "~> 5.1.0" gem "sqlite3", "~> 1.3.13" gem "tzinfo-data" -gemspec path: "../../" +gemspec path: "../" diff --git a/gemfiles/rails_5.2.gemfile b/gemfiles/rails_5.2.gemfile index 5f75d71d..0c9f9b03 100644 --- a/gemfiles/rails_5.2.gemfile +++ b/gemfiles/rails_5.2.gemfile @@ -6,4 +6,4 @@ gem "rails", "~> 5.2.0" gem "sqlite3", "~> 1.3.13" gem "tzinfo-data" -gemspec path: "../../" +gemspec path: "../" diff --git a/gemfiles/rails_6.gemfile b/gemfiles/rails_6.gemfile index b2bdc3e1..1ca774ca 100644 --- a/gemfiles/rails_6.gemfile +++ b/gemfiles/rails_6.gemfile @@ -6,4 +6,4 @@ gem "rails", "~> 6" gem "sqlite3", "~> 1.4" gem "tzinfo-data" -gemspec path: "../../" +gemspec path: "../" From a19cb5a0e0d7ee63ceb4a46b0e76b3df1a5e5038 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 14:25:15 +0100 Subject: [PATCH 132/143] downgrade bundler on 4.2 --- .drone.yml | 3 +-- Appraisals | 2 +- gemfiles/rails_4.2.gemfile | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.drone.yml b/.drone.yml index 08c7d768..1875115d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -38,7 +38,7 @@ concurrency: steps: - name: build_rails4 - image: ruby:2.7-alpine + image: ruby:2.5-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile <<: [*vault_tag, *artifactory_credentials] @@ -48,7 +48,6 @@ steps: image: ruby:2.7-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile - DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands diff --git a/Appraisals b/Appraisals index 1b8a0bbf..822c561e 100644 --- a/Appraisals +++ b/Appraisals @@ -1,7 +1,7 @@ appraise 'rails-4.2' do gem 'rails', '~> 4.2.0' gem 'sqlite3', '~> 1.3.13' - gem 'bundler', '~> 1.17.3' + gem 'bundler', '~> 1.17.2' end appraise 'rails-5.0' do diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index 510b3205..b12969a5 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -4,6 +4,6 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" gem "sqlite3", "~> 1.3.13" -gem "bundler", "~> 1.17.3" +gem "bundler", "~> 1.17.2" gemspec path: "../" From 0f6c53430114fbd54a8ebc2db6e45ce564c8deff Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 14:39:33 +0100 Subject: [PATCH 133/143] Add txinfo into ruby 4 --- .drone.yml | 16 +++++++++++++++- Appraisals | 1 + fc-vault-rails.gemspec | 1 + gemfiles/rails_4.2.gemfile | 1 + 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 1875115d..b016189f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -38,7 +38,7 @@ concurrency: steps: - name: build_rails4 - image: ruby:2.5-alpine + image: ruby:2.6-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_4.2.gemfile <<: [*vault_tag, *artifactory_credentials] @@ -50,6 +50,8 @@ steps: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile <<: [*vault_tag, *artifactory_credentials] commands: *commands + depends_on: + - build_rails4 - name: build_rails51 image: ruby:2.7-alpine @@ -58,6 +60,9 @@ steps: DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands + depends_on: + - build_rails4 + - build_rails5 - name: build_rails52 image: ruby:2.7-alpine @@ -66,6 +71,10 @@ steps: DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands + depends_on: + - build_rails4 + - build_rails5 + - build_rails51 - name: build_rails6 image: ruby:3.0-alpine @@ -74,6 +83,11 @@ steps: DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands + depends_on: + - build_rails4 + - build_rails5 + - build_rails51 + - build_rails52 - name: publish_feature_branch_gem image: quay.io/fundingcircle/alpine-ruby-builder:2.7 diff --git a/Appraisals b/Appraisals index 822c561e..aa22d6ac 100644 --- a/Appraisals +++ b/Appraisals @@ -2,6 +2,7 @@ appraise 'rails-4.2' do gem 'rails', '~> 4.2.0' gem 'sqlite3', '~> 1.3.13' gem 'bundler', '~> 1.17.2' + gem 'tzinfo-data' end appraise 'rails-5.0' do diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index c98042eb..dd32e8be 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |s| s.add_dependency "activerecord", ">= 4.2" s.add_dependency "vault", "~> 0.7" + s.add_dependency "tzinfo-data" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" diff --git a/gemfiles/rails_4.2.gemfile b/gemfiles/rails_4.2.gemfile index b12969a5..b70b0322 100644 --- a/gemfiles/rails_4.2.gemfile +++ b/gemfiles/rails_4.2.gemfile @@ -5,5 +5,6 @@ source "https://rubygems.org" gem "rails", "~> 4.2.0" gem "sqlite3", "~> 1.3.13" gem "bundler", "~> 1.17.2" +gem "tzinfo-data" gemspec path: "../" From 5e89c6f1629fd0def4f671d7fe23756b0e4b0fb7 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 14:44:40 +0100 Subject: [PATCH 134/143] disable the check on the database --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index b016189f..bc19b0ef 100644 --- a/.drone.yml +++ b/.drone.yml @@ -48,6 +48,7 @@ steps: image: ruby:2.7-alpine environment: BUNDLE_GEMFILE: gemfiles/rails_5.0.gemfile + DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 <<: [*vault_tag, *artifactory_credentials] commands: *commands depends_on: From db94ebe60c100604ba543fbd919f44c953a0bf5d Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Fri, 15 Sep 2023 14:50:42 +0100 Subject: [PATCH 135/143] Fixed typo --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index bc19b0ef..0638e107 100644 --- a/.drone.yml +++ b/.drone.yml @@ -103,7 +103,7 @@ steps: curl -u "$ARTIFACTORY_USER":"$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases/api/v1/api_key.yaml > ~/.gem/credentials chmod 600 ~/.gem/credentials - | - package=$(ls -t1 active-codas*.gem | head -1) + package=$(ls -t1 fc-vault-rails*.gem | head -1) gem push $package --host https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-pre-releases depends_on: - build_rails4 From df0d09144b8cb5c8b7bce28da4595d0ae8e6b83f Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Mon, 11 Sep 2023 15:54:18 +0100 Subject: [PATCH 136/143] Added rails 7 --- .drone.yml | 2 +- Appraisals | 5 +++++ CHANGELOG.md | 5 +++++ fc-vault-rails.gemspec | 6 +++--- gemfiles/.bundle/config | 1 + gemfiles/rails_7.gemfile | 8 ++++++++ 6 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 gemfiles/rails_7.gemfile diff --git a/.drone.yml b/.drone.yml index 0638e107..4a140c19 100644 --- a/.drone.yml +++ b/.drone.yml @@ -187,4 +187,4 @@ steps: env: production locale: UK depends_on: - - publish_master_gem \ No newline at end of file + - publish_master_gem diff --git a/Appraisals b/Appraisals index aa22d6ac..c160b090 100644 --- a/Appraisals +++ b/Appraisals @@ -28,3 +28,8 @@ appraise 'rails-6' do gem 'sqlite3', '~> 1.4' gem 'tzinfo-data' end + +appraise "rails-7" do + gem "rails", "~> 7" + gem 'sqlite3', '~> 1.4' +end diff --git a/CHANGELOG.md b/CHANGELOG.md index 53ea2b13..3171a906 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Vault Rails Changelog +## 2.1.2 (September 8, 2023) + +IMPROVEMENTS +- Add Rails 7 Support + ## 2.1.1 (January 31, 2022) NEW FEATURES diff --git a/fc-vault-rails.gemspec b/fc-vault-rails.gemspec index dd32e8be..dfa6dd9d 100644 --- a/fc-vault-rails.gemspec +++ b/fc-vault-rails.gemspec @@ -17,17 +17,17 @@ Gem::Specification.new do |s| s.files = Dir["{app,config,db,lib}/**/*", "LICENSE", "Rakefile", "README.md"] s.test_files = Dir["spec/**/*"] - s.add_dependency "activerecord", ">= 4.2" + s.add_dependency "activerecord" s.add_dependency "vault", "~> 0.7" s.add_dependency "tzinfo-data" s.add_development_dependency "appraisal", "~> 2.1" s.add_development_dependency "bundler" - s.add_development_dependency "rails", ">= 4.2" + s.add_development_dependency "rails", "~> 6" s.add_development_dependency "byebug" s.add_development_dependency "pry" s.add_development_dependency "rake" s.add_development_dependency "rspec", "~> 3.2" - s.add_development_dependency "sqlite3", '~> 1.3' + s.add_development_dependency "sqlite3", '~> 1.4' s.add_development_dependency "oj" end diff --git a/gemfiles/.bundle/config b/gemfiles/.bundle/config index c127f802..1d367dbc 100644 --- a/gemfiles/.bundle/config +++ b/gemfiles/.bundle/config @@ -1,2 +1,3 @@ --- BUNDLE_RETRY: "1" +BUNDLE_WITH: "development" diff --git a/gemfiles/rails_7.gemfile b/gemfiles/rails_7.gemfile new file mode 100644 index 00000000..1caf2486 --- /dev/null +++ b/gemfiles/rails_7.gemfile @@ -0,0 +1,8 @@ +# This file was generated by Appraisal + +source "https://rubygems.org" + +gem "rails", "~> 7" +gem "sqlite3", "~> 1.4" + +gemspec path: "../" From b4e48e476131b3a17f0a4dc2fcc4a96a538e2a50 Mon Sep 17 00:00:00 2001 From: Ismael Marin Molina Date: Mon, 25 Sep 2023 15:56:20 +0100 Subject: [PATCH 137/143] Added update warning in the README --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index b1be0e39..9fc93957 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ Vault Rails [![CircleCI](https://circleci.com/gh/FundingCircle/vault-rails/tree/master.svg?style=svg)](https://circleci.com/gh/FundingCircle/vault-rails/tree/master) =========== +> [!IMPORTANT] +> Library has been recently updated to provide support to Rails 7, it was technically tested but if you find error integrating it, drop a message in the +> [\#customerdata team channel](https://fundingcircle.slack.com/archives/C03PNLY44M6) + Vault is the official Rails plugin for interacting with [Vault](https://vaultproject.io) by HashiCorp. **The documentation in this README corresponds to the master branch of the Vault Rails plugin. It may contain unreleased features or different APIs than the most recently released version. Please see the Git tag that corresponds to your version of the Vault Rails plugin for the proper documentation.** From 07884a03fef4fe7e60fab7b488439d7f2d8b300d Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Fri, 17 Nov 2023 16:13:14 +0200 Subject: [PATCH 138/143] Fix deprecation warning when using connection_config ``` ActiveRecord::Base.try(:connection_db_config).try(:adapter) => "postgresql" ActiveRecord::Base.try(:connection_config)[:adapter] W, [2023-11-17T16:12:59.253878+02:00 #22587] DEPRECATION WARNING: connection_config is deprecated and will be removed from Rails 7.0 (Use connection_db_config instead) (called from
at (pry):6) => "postgresql" ``` --- lib/vault/latest/encrypted_model.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/vault/latest/encrypted_model.rb b/lib/vault/latest/encrypted_model.rb index 1bdd12ab..14f229cd 100644 --- a/lib/vault/latest/encrypted_model.rb +++ b/lib/vault/latest/encrypted_model.rb @@ -167,10 +167,12 @@ def _vault_fetch_attribute_type(options) attribute_type = options.fetch(:type, ActiveRecord::Type::Value.new) if attribute_type.is_a?(Symbol) - if ActiveRecord::Base.connection_config[:adapter] - ActiveRecord::Type.lookup(attribute_type, adapter: ActiveRecord::Base.connection_config[:adapter]) + adapter = ActiveRecord::Base.try(:connection_db_config).try(:adapter) || (ActiveRecord::Base.try(:connection_config) || {})[:adapter] + + if adapter + ActiveRecord::Type.lookup(attribute_type, adapter: adapter) else - ActiveRecord::Type.lookup(attribute_type) # This call does a db connection, best find a way to configure `ActiveRecord::Base.connection_config[:adapter]` + ActiveRecord::Type.lookup(attribute_type) # This call does a db connection, best find a way to configure the adapter end else ActiveModel::Type::Value.new From 679ca5e6eecb6afb8ca90635e6dc6aa2a4478b80 Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Fri, 17 Nov 2023 16:59:28 +0200 Subject: [PATCH 139/143] Add rails 7 build --- .drone.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.drone.yml b/.drone.yml index 4a140c19..51ddd2a5 100644 --- a/.drone.yml +++ b/.drone.yml @@ -90,6 +90,20 @@ steps: - build_rails51 - build_rails52 + - name: build_rails7 + image: ruby:3.0-alpine + environment: + BUNDLE_GEMFILE: gemfiles/rails_7.gemfile + DISABLE_DATABASE_ENVIRONMENT_CHECK: 1 + <<: [*vault_tag, *artifactory_credentials] + commands: *commands + depends_on: + - build_rails4 + - build_rails5 + - build_rails51 + - build_rails52 + - build_rails6 + - name: publish_feature_branch_gem image: quay.io/fundingcircle/alpine-ruby-builder:2.7 environment: @@ -111,6 +125,7 @@ steps: - build_rails51 - build_rails52 - build_rails6 + - build_rails7 when: branch: exclude: From 83ca0199d680e5372b0374f4a8dcea86e0af2b2b Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Fri, 17 Nov 2023 16:52:32 +0200 Subject: [PATCH 140/143] Update version to 2.1.2 Add rails 7 support Fixes DEPRECATION WARNING: connection_config is deprecated and will be removed from Rails 7.0 --- CHANGELOG.md | 3 ++- lib/vault/rails/version.rb | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3171a906..ee2ca263 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Vault Rails Changelog -## 2.1.2 (September 8, 2023) +## 2.1.2 (November 17, 2023) IMPROVEMENTS - Add Rails 7 Support +- Fix "DEPRECATION WARNING: connection_config is deprecated and will be removed from Rails 7.0" ## 2.1.1 (January 31, 2022) diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index b1997af0..1c07fb74 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = "2.1.1" + VERSION = '2.1.2' def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') From 6497f8c1479480e45de52458166eb005040a667b Mon Sep 17 00:00:00 2001 From: Aleksandar Ivanov Date: Tue, 21 Nov 2023 09:52:57 +0200 Subject: [PATCH 141/143] Fix bin/check_gem_version and make it executable --- bin/check_gem_version | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) mode change 100644 => 100755 bin/check_gem_version diff --git a/bin/check_gem_version b/bin/check_gem_version old mode 100644 new mode 100755 index 74efbd3d..2ba2ec81 --- a/bin/check_gem_version +++ b/bin/check_gem_version @@ -1,6 +1,6 @@ echo -n "Checking existing available versions of gem... "; current_version="$(ruby -e 'require "./lib/vault/rails/version.rb";puts Vault::Rails::VERSION')"; -version_info_json=$(curl -s -u "$ARTIFACTORY_USER":"$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local/api/v1/versions/active-codas.json); +version_info_json=$(curl -s -u "$ARTIFACTORY_USER":"$ARTIFACTORY_PASSWORD" https://fundingcircle.jfrog.io/fundingcircle/api/gems/rubygems-local/api/v1/versions/fc-vault-rails.json); if echo "$version_info_json" | jq -e '[.[] | select(.number|test("'$current_version'"))]|length==0' > /dev/null; then echo -e '\e[32mOK\e[0m'; @@ -9,6 +9,6 @@ else echo 'Existing published versions:'; echo "$version_info_json" | jq 'map(.number)'; echo -e 'Your version: \e[31m'$current_version'\e[0m.'; - echo 'Please bump the version in `lib/active_codas/version.rb`.' + echo 'Please bump the version in `lib/vault/rails/version.rb`.' exit 1; -fi \ No newline at end of file +fi From 209e7946d28730e442139a01a1b26d3cfb81876b Mon Sep 17 00:00:00 2001 From: Ellis Pritchard Date: Fri, 28 Jun 2024 12:47:25 +0100 Subject: [PATCH 142/143] add batch methods to TransitJsonCodec --- CHANGELOG.md | 5 +++ lib/vault/rails/version.rb | 2 +- lib/vault/transit_json_codec.rb | 22 ++++++++++ spec/unit/transit_json_codec_spec.rb | 63 ++++++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2ca263..113cdef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Vault Rails Changelog +## 2.2.0 (June 28, 2024) +* Adds `TransitJsonCodec.batch_*` functions which use the Vault batch API to process + an array of values with one API call. + * like `Vault::Rails.batch_decrypt` but with `transit` and FC's standard JSON pre-encoding of values. + ## 2.1.2 (November 17, 2023) IMPROVEMENTS diff --git a/lib/vault/rails/version.rb b/lib/vault/rails/version.rb index 1c07fb74..ebd63e13 100644 --- a/lib/vault/rails/version.rb +++ b/lib/vault/rails/version.rb @@ -1,6 +1,6 @@ module Vault module Rails - VERSION = '2.1.2' + VERSION = '2.2.0' def self.latest? ActiveRecord.version >= Gem::Version.new('5.0.0') diff --git a/lib/vault/transit_json_codec.rb b/lib/vault/transit_json_codec.rb index 5c32bbb7..869ec024 100644 --- a/lib/vault/transit_json_codec.rb +++ b/lib/vault/transit_json_codec.rb @@ -18,6 +18,17 @@ def encrypt(plaintext) secret.data[:ciphertext] end + def batch_encrypt(plaintexts) + return [] if plaintexts.blank? + + secrets = Vault.logical.write( + "transit/encrypt/#{key}", + batch_input: plaintexts.map { |plaintext| { plaintext: Base64.strict_encode64(Oj.dump(plaintext)) } } + ) + + secrets.data[:batch_results].map { |result| result[:ciphertext] } + end + def decrypt(ciphertext) return if ciphertext.blank? @@ -29,6 +40,17 @@ def decrypt(ciphertext) Oj.load(Base64.strict_decode64(secret.data[:plaintext])) end + def batch_decrypt(ciphertexts) + return [] if ciphertexts.blank? + + secret = Vault.logical.write( + "transit/decrypt/#{key}", + batch_input: ciphertexts.map { |ciphertext| { ciphertext: ciphertext } } + ) + + secret.data[:batch_results].map { |result| Oj.load(Base64.strict_decode64(result[:plaintext])) } + end + private attr_reader :key diff --git a/spec/unit/transit_json_codec_spec.rb b/spec/unit/transit_json_codec_spec.rb index a1e6073f..8ea401db 100644 --- a/spec/unit/transit_json_codec_spec.rb +++ b/spec/unit/transit_json_codec_spec.rb @@ -85,4 +85,67 @@ end end end + + describe '#batch_encrypt' do + context 'when plaintexts array is empty' do + it 'returns empty array' do + expect(codec.batch_encrypt([])).to eq([]) + expect(codec.batch_encrypt(nil)).to eq([]) + end + end + + context 'when plaintexts are present' do + let(:plaintexts) { ['some text', 'other text'] } + + it 'returns array of encrypted values' do + ciphertexts = codec.batch_encrypt(plaintexts) + expect(ciphertexts).not_to be_blank + expect(ciphertexts.length).to eq(plaintexts.length) + ciphertexts.each { |ciphertext| expect(ciphertext).to start_with('vault:v1:') } + ciphertexts.each_with_index { |ciphertext, i| expect(codec.decrypt(ciphertext)).to eq(plaintexts[i]) } + end + + context 'when encryption fails' do + before do + allow(Vault).to receive(:logical).and_raise(StandardError, 'Oh no!') + end + + it 're-raises error' do + expect { codec.batch_encrypt(plaintexts) }.to raise_error(StandardError) + end + end + end + end + + describe '#batch_decrypt' do + context 'when ciphertexts array is empty' do + it 'returns empty array' do + expect(codec.batch_decrypt([])).to eq([]) + expect(codec.batch_decrypt(nil)).to eq([]) + end + end + + context 'when ciphertexts are present' do + let(:plaintexts) { ['some text', 'other text'] } + let(:ciphertexts) { codec.batch_encrypt(plaintexts) } + + it 'returns array of decrypted values' do + decrypted = codec.batch_decrypt(ciphertexts) + expect(decrypted).not_to be_blank + expect(decrypted.length).to eq(plaintexts.length) + expect(decrypted).to eq(plaintexts) + end + + context 'when decryption fails' do + before do + allow(Vault).to receive(:logical).and_raise(StandardError, 'Oh no!') + end + + it 're-raises error' do + expect { codec.batch_decrypt(plaintexts) }.to raise_error(StandardError) + end + end + end + end + end From 9799158ecfc133aeceb8116125d500a1c81a314b Mon Sep 17 00:00:00 2001 From: Sarah O'Grady Date: Mon, 15 Jul 2024 19:10:10 +0100 Subject: [PATCH 143/143] change team name to team-customer-sme --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bda6f0ff..0718e925 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @FundingCircle/team-customer-data \ No newline at end of file +* @FundingCircle/team-customer-sme