thekompanee/chamber

View on GitHub
lib/chamber/filters/encryption_filter.rb

Summary

Maintainability
A
1 hr
Test Coverage
# frozen_string_literal: true

require 'openssl'
require 'yaml'
require 'chamber/encryption_methods/public_key'
require 'chamber/encryption_methods/ssl'
require 'chamber/encryption_methods/none'
require 'chamber/refinements/deep_dup'

module    Chamber
module    Filters
class     EncryptionFilter
  using ::Chamber::Refinements::DeepDup

  BASE64_STRING_PATTERN     = %r{\A[A-Za-z0-9+/]{342}==\z}.freeze
  BASE64_SUBSTRING_PATTERN  = %r{[A-Za-z0-9+/#]*={0,2}}.freeze
  LARGE_DATA_STRING_PATTERN = /
                                \A
                                (#{BASE64_SUBSTRING_PATTERN})
                                \#
                                (#{BASE64_SUBSTRING_PATTERN})
                                \#
                                (#{BASE64_SUBSTRING_PATTERN})
                                \z
                              /x.freeze

  attr_accessor :data,
                :secure_key_token
  attr_reader   :encryption_keys

  def self.execute(**args)
    new(**args).__send__(:execute)
  end

  def initialize(data:, secure_key_prefix:, encryption_keys: {}, **_args)
    self.encryption_keys  = (encryption_keys || {}).transform_keys(&:to_s)
    self.data             = data.deep_dup
    self.secure_key_token = /\A#{Regexp.escape(secure_key_prefix)}/
  end

  protected

  def execute(raw_data = data, namespace = nil)
    raw_data.each_with_object({}) do |(key, value), settings|
      settings[key] = if value.respond_to? :each_pair
                        execute(value, namespace || key)
                      elsif key.match(secure_key_token)
                        encrypt(namespace, key, value)
                      else
                        value
                      end
    end
  end

  def encryption_keys=(other)
    @encryption_keys = other.each_with_object({}) do |(namespace, keyish), memo| # rubocop:disable Style/HashTransformValues
      memo[namespace] = if keyish.is_a?(OpenSSL::PKey::RSA)
                          keyish
                        elsif ::File.readable?(::File.expand_path(keyish))
                          file_contents = ::File.read(::File.expand_path(keyish))
                          OpenSSL::PKey::RSA.new(file_contents)
                        else
                          OpenSSL::PKey::RSA.new(keyish)
                        end
    end
  end

  private

  def encrypt(namespace, key, value)
    method         = encryption_method(value)
    encryption_key = encryption_keys[namespace] || encryption_keys['__default']

    return value unless encryption_key

    method.encrypt(key, value, encryption_key)
  end

  def encryption_method(value)
    value_is_encrypted = value.is_a?(::String) &&
                           (value.match(BASE64_STRING_PATTERN) ||
                            value.match(LARGE_DATA_STRING_PATTERN))

    if value_is_encrypted
      EncryptionMethods::None
    else
      serialized_value = YAML.dump(value)

      if serialized_value.length <= 128
        EncryptionMethods::PublicKey
      else
        EncryptionMethods::Ssl
      end
    end
  end
end
end
end