module Vault::EncryptedModel::ClassMethods

Public Instance Methods

__vault_attributes() click to toggle source

The list of Vault attributes.

@return [Hash]

# File lib/vault/encrypted_model.rb, line 101
def __vault_attributes
  @vault_attributes ||= {}
end
_vault_validate_options!(options) click to toggle source

Validate that Vault options are all a-okay! This method will raise exceptions if something does not make sense.

# File lib/vault/encrypted_model.rb, line 107
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

    if options[:transform_secret]
      raise Vault::Rails::ValidationFailedError, "Cannot use the " \
        "transform secrets engine with a specified `:serializer'!"
    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

  if context = options[:context]
    if context.is_a?(Proc) && context.arity != 1
      raise Vault::Rails::ValidationFailedError, "Proc passed to " \
        "`:context' must take 1 argument!"
    end
  end
  if transform_opts = options[:transform_secret]
    if !transform_opts[:transformation]
      raise Vault::Rails::VaildationFailedError, "Transform Secrets " \
        "requires a transformation name!"
    end
  end
end
vault_attribute(attribute, options = {}) click to toggle source

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 [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 [String, Symbol, Proc] :context

either a string context, or a symbol or proc used to generate a
context for key generation

@option options [Object] :default

a default value for this attribute to be set to if the underlying
value is nil

@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

@option options [Hash] :transform_secret

a hash providing details about the transformation to use,
this includes the name, and the role to use
Calls superclass method
# File lib/vault/encrypted_model.rb, line 50
def vault_attribute(attribute, options = {})
  # Sanity check options!
  _vault_validate_options!(options)

  parsed_opts = if options[:transform_secret]
                  parse_transform_secret_attributes(attribute, options)
                else
                  parse_transit_attributes(attribute, options)
                end
  parsed_opts[:encrypted_column] = options[:encrypted_column] || "#{attribute}_encrypted"

  # Make a note of this attribute so we can use it in the future (maybe).
  __vault_attributes[attribute.to_sym] = parsed_opts

  self.attribute attribute.to_s, ActiveRecord::Type::Value.new,
    default: nil

  # Getter
  define_method("#{attribute}") do
    self.__vault_load_attributes!(attribute) unless @__vault_loaded
    super()
  end

  # Setter
  define_method("#{attribute}=") do |value|
    self.__vault_load_attributes!(attribute) 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 sent back regardless.

    attribute_will_change!("#{attribute}")
    instance_variable_set("@#{attribute}", value)
    super(value)

    # Return the value to be consistent with other AR methods.
    value
  end

  # Checker
  define_method("#{attribute}?") do
    self.__vault_load_attributes!(attribute) unless @__vault_loaded
    instance_variable_get("@#{attribute}").present?
  end

  self
end
vault_lazy_decrypt() click to toggle source
# File lib/vault/encrypted_model.rb, line 144
def vault_lazy_decrypt
  @vault_lazy_decrypt ||= false
end
vault_lazy_decrypt!() click to toggle source
# File lib/vault/encrypted_model.rb, line 148
def vault_lazy_decrypt!
  @vault_lazy_decrypt = true
end
vault_single_decrypt() click to toggle source
# File lib/vault/encrypted_model.rb, line 152
def vault_single_decrypt
  @vault_single_decrypt ||= false
end
vault_single_decrypt!() click to toggle source
# File lib/vault/encrypted_model.rb, line 156
def vault_single_decrypt!
  @vault_single_decrypt = true
end

Private Instance Methods

parse_transform_secret_attributes(attribute, options) click to toggle source
# File lib/vault/encrypted_model.rb, line 162
def parse_transform_secret_attributes(attribute, options)
  opts = {}
  opts[:transform_secret] = true

  serializer = Class.new
  serializer.define_singleton_method(:encode) do |raw|
    return if raw.nil?
    resp = Vault::Rails.transform_encode(raw, options[:transform_secret])
    resp.dig(:data, :encoded_value)
  end
  serializer.define_singleton_method(:decode) do |raw|
    return if raw.nil?
    resp = Vault::Rails.transform_decode(raw, options[:transform_secret])
    resp.dig(:data, :decoded_value)
  end
  opts[:serializer] = serializer
  opts
end
parse_transit_attributes(attribute, options) click to toggle source
# File lib/vault/encrypted_model.rb, line 181
def parse_transit_attributes(attribute, options)
  opts = {}
  opts[:path] = options[:path] || "transit"
  opts[:key] = options[:key] || "#{Vault::Rails.application}_#{table_name}_#{attribute}"
  opts[:context] = options[:context]
  opts[:default] = options[:default]

  # 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

  opts[:serializer] = serializer
  opts
end