diff --git a/.rubocop.yml b/.rubocop.yml index 0fb5f97..8b36803 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -41,6 +41,7 @@ Style/StringLiterals: Metrics/AbcSize: Exclude: - db/migrate/**/* + - app/services/crypt_serializer.rb Metrics/LineLength: Max: 110 diff --git a/app/models/account.rb b/app/models/account.rb index c17a874..5dc3232 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -1,2 +1,3 @@ class Account < ApplicationRecord + serialize :password, CryptSerializer end diff --git a/app/services/crypt_serializer.rb b/app/services/crypt_serializer.rb new file mode 100644 index 0000000..ef391c6 --- /dev/null +++ b/app/services/crypt_serializer.rb @@ -0,0 +1,43 @@ +require 'openssl' +require 'base64' + +class CryptSerializer + attr_reader :cipher + + class << self + # pulling from DB - return plain value + def load value + new.decrypt value + end + + # saving to DB - return encrypted value + def dump value + new.encrypt value + end + end + + def initialize + @cipher = OpenSSL::Cipher::AES.new(256, :CBC) + end + + def encrypt(value) + return value if value.nil? + unless value.is_a?(String) + raise "Attribute was supposed to be a `String`, but was instead a `#{value.class}`" + end + + cipher.encrypt + parts = [cipher.random_key, cipher.random_iv, cipher.update(value) + cipher.final] + Base64.urlsafe_encode64 Marshal.dump(parts) + end + + def decrypt(value) + return value if value.nil? + + parts = Marshal.load Base64.urlsafe_decode64(value) + cipher.decrypt + cipher.key = parts[0] + cipher.iv = parts[1] + cipher.update(parts[2]) + cipher.final + end +end diff --git a/test/controllers/accounts_controller_test.rb b/test/controllers/accounts_controller_test.rb index 6daa963..2a056f7 100644 --- a/test/controllers/accounts_controller_test.rb +++ b/test/controllers/accounts_controller_test.rb @@ -2,7 +2,7 @@ require 'test_helper' class AccountsControllerTest < ActionDispatch::IntegrationTest setup do - @account = accounts(:client1) + @account = accounts(:account1) end test "should get index" do diff --git a/test/fixtures/accounts.yml b/test/fixtures/accounts.yml index 96a63f8..42981f4 100644 --- a/test/fixtures/accounts.yml +++ b/test/fixtures/accounts.yml @@ -1,13 +1,7 @@ # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html -client1: +account1: username: client-one - password: 1q2w3e4r5t6y7u + password: <%= CryptSerializer.dump('1q2w3e4r5t6y7u') %> home: client_one site: dev - -cleint2: - username: client-two - password: lokjnmjht75erfhj - home: client_two - site: dev diff --git a/test/services/crypt_serializer_test.rb b/test/services/crypt_serializer_test.rb new file mode 100644 index 0000000..f885dcd --- /dev/null +++ b/test/services/crypt_serializer_test.rb @@ -0,0 +1,30 @@ +require 'test_helper' + +class CryptSerializerTest < ActiveSupport::TestCase + test "should generate marshaled array" do + string = "some string to encrypt" + encrypted = CryptSerializer.dump string + ar = Marshal.load(Base64.urlsafe_decode64(encrypted)) + + assert_instance_of Array, ar + assert_equal 3, ar.count + end + + test "should encrypt and dencrypt" do + string = "test@string.email" + encrypted = CryptSerializer.dump string + decrypted = CryptSerializer.load encrypted + + assert_equal string, decrypted + end + + test "should handle nils" do + assert_equal nil, CryptSerializer.dump(nil) + end + + test "must raise RuntimeError" do + assert_raises RuntimeError do + CryptSerializer.dump [] + end + end +end