Module: AttrRedactor

Defined in:
lib/attr_redactor.rb,
lib/attr_redactor/version.rb,
lib/attr_redactor/adapters/data_mapper.rb,
lib/attr_redactor/adapters/active_record.rb

Overview

Adds attr_accessors that redact an object’s attributes

Defined Under Namespace

Modules: Adapters, InstanceMethods, Version

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *arguments, &block) ⇒ Object

Forwards calls to :redact_#attribute or :unredact_#attribute to the corresponding redact or unredact method if attribute was configured with attr_redactor

Example

class User
  attr_redactor :data, key: 'my secret key'
end

User.redact_data('SOME_ENCRYPTED_EMAIL_STRING')


229
230
231
232
233
234
235
# File 'lib/attr_redactor.rb', line 229

def method_missing(method, *arguments, &block)
  if method.to_s =~ /^(redact|unredact)_(.+)$/ && attr_redacted?($2)
    send($1, $2, *arguments)
  else
    super
  end
end

Class Method Details

.extended(base) ⇒ Object

:nodoc:



7
8
9
10
11
12
13
# File 'lib/attr_redactor.rb', line 7

def self.extended(base) # :nodoc:
  base.class_eval do
    include InstanceMethods
    attr_writer :attr_redactor_options
    @attr_redactor_options, @redacted_attributes = {}, {}
  end
end

Instance Method Details

#attr_redacted?(attribute) ⇒ Boolean

Checks if an attribute is configured with attr_redactor

Example

class User
  attr_accessor :name
  attr_redactor :email
end

User.attr_redacted?(:name)  # false
User.attr_redacted?(:email) # true

Returns:

  • (Boolean)


164
165
166
# File 'lib/attr_redactor.rb', line 164

def attr_redacted?(attribute)
  redacted_attributes.has_key?(attribute.to_sym)
end

#attr_redactor(*attributes) ⇒ Object

Generates attr_accessors that remove, digest or encrypt values in an attribute that is a hash transparently

Options Any other options you specify are passed on to hash_redactor which is used for redacting, and in turn onto attr_encrypted which it uses for encryption)

redact:	        Hash that describes the values to redact in the hash. Should be a

map of the form { key => method }, where key is the key to redact and method is one of :remove, :digest, :encrypt eg { :ssn => :remove, :email => :digest :medical_notes => :encrypt, }

attribute:        The name of the referenced encrypted attribute. For example
                  <tt>attr_accessor :data, attribute: :safe_data</tt> would generate 
                  an attribute named 'safe_data' to store the redacted data.

This is useful when defining one attribute to encrypt at a time or when the :prefix and :suffix options aren’t enough.

                  Defaults to nil.

prefix:           A prefix used to generate the name of the referenced redacted attributes.
                  For example <tt>attr_accessor :data, prefix: 'safe_'</tt> would
                  generate attributes named 'safe_data' to store the redacted
                  data hash.
                  Defaults to 'redacted_'.

suffix:           A suffix used to generate the name of the referenced redacted attributes.
                  For example <tt>attr_accessor :data, prefix: '', suffix: '_cleaned'</tt>
                  would generate attributes named 'data_cleaned' to store the
                  cleaned up data.
                  Defaults to ''.

encryption_key:   The encryption key to use for encrypted fields.
                  Defaults to nil. Required if you are using encryption.

digest_salt:		The salt to use for digests

Defaults to “”

You can specify your own default options

class User
  attr_redactor_options.merge!(redact: { :ssn => :remove })
  attr_redactor :data
end

Example

class User
  attr_redactor_options.merge!(encryption_key: 'some secret key')
  attr_redactor :data, redact: {

:ssn => :remove, :email => :digest :medical_notes => :encrypt, }

end

@user = User.new
@user.redacted_data # nil
@user.data? # false
@user.data = { ssn: 'private', email: '[email protected]', medical_notes: 'private' }
@user.data? # true
@user.redacted_data # { email_digest: 'XXXXXX', encrypted_medical_notes: 'XXXXXX', encrypted_medical_notes_iv: 'XXXXXXX' }
@user.save!
@user = User.last

@user.data # { email_digest: 'XXXXXX', medical_notes: 'private' }

See README for more examples


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/attr_redactor.rb', line 87

def attr_redactor(*attributes)
  options = attributes.last.is_a?(Hash) ? attributes.pop : {}
  options = attr_redactor_default_options.dup.merge!(attr_redactor_options).merge!(options)

  attributes.each do |attribute|
    redacted_attribute_name = (options[:attribute] ? options[:attribute] : [options[:prefix], attribute, options[:suffix]].join).to_sym

    instance_methods_as_symbols = attribute_instance_methods_as_symbols
    attr_reader redacted_attribute_name unless instance_methods_as_symbols.include?(redacted_attribute_name)
    attr_writer redacted_attribute_name unless instance_methods_as_symbols.include?(:"#{redacted_attribute_name}=")

 hash_redactor_options = options.dup

 # Don't pass the filter mode in, in case it's dynamic
 hash_redactor_options.delete :filter_mode

 # Create a redactor for the attribute
 options[:redactor] = HashRedactor::HashRedactor.new(hash_redactor_options)

    define_method(attribute) do
      instance_variable_get("@#{attribute}") || instance_variable_set("@#{attribute}", unredact(attribute, send(redacted_attribute_name)))
    end

    define_method("#{attribute}=") do |value|
      send("#{redacted_attribute_name}=", redact(attribute, value))
      instance_variable_set("@#{attribute}", value)
      # replace with redacted/unredacted value immediately
      instance_variable_set("@#{attribute}", unredact(attribute, send(redacted_attribute_name)))
    end

    define_method("#{attribute}?") do
      value = send(attribute)
      value.respond_to?(:empty?) ? !value.empty? : !!value
    end

    define_method("#{attribute}_redact_hash") do
   options = redacted_attributes[attribute]
    	options[:redact]
    end

    redacted_attributes[attribute.to_sym] = options.merge(attribute: redacted_attribute_name)
  end
end

#attr_redactor_optionsObject

Default options to use with calls to attr_redactor

It will inherit existing options from its superclass



134
135
136
# File 'lib/attr_redactor.rb', line 134

def attr_redactor_options
  @attr_redactor_options ||= superclass.attr_redactor_options.dup
end

#redact(attribute, value, options = {}) ⇒ Object

Redacts for the attribute specified

Example

class User
  attr_redactor :data
end

redacted_data = User.redact(:data, { email: '[email protected]' })


196
197
198
199
200
201
202
203
# File 'lib/attr_redactor.rb', line 196

def redact(attribute, value, options = {})
  options = redacted_attributes[attribute.to_sym].merge(options)
  if options[:if] && !options[:unless] && !value.nil?
    redacted_value = options[:redactor].redact(value, options)
  else
    value
  end
end

#redacted_attributesObject

Contains a hash of redacted attributes with virtual attribute names as keys and their corresponding options as values

Example

class User
  attr_redactor :data, key: 'my secret key'
end

User.redacted_attributes # { data: { attribute: 'redacted_data', encryption_key: 'my secret key' } }


215
216
217
# File 'lib/attr_redactor.rb', line 215

def redacted_attributes
  @redacted_attributes ||= superclass.redacted_attributes.dup
end

#unredact(attribute, redacted_value, options = {}) ⇒ Object

Decrypts values in the attribute specified

Example

class User
  attr_redactor :data
end

data = User.redact(:data, SOME_REDACTED_HASH)


177
178
179
180
181
182
183
184
185
# File 'lib/attr_redactor.rb', line 177

def unredact(attribute, redacted_value, options = {})
  options = redacted_attributes[attribute.to_sym].merge(options)
  if options[:if] && !options[:unless] && !redacted_value.nil?
    value = options[:redactor].decrypt(redacted_value, options)
    value
  else
    redacted_value
  end
end