Class: Omen::Column

Inherits:
Object
  • Object
show all
Defined in:
app/models/omen/column.rb

Overview

One column of the schema that Rails encrypts before the database is allowed to store it.

Constant Summary collapse

HIDDEN =

What stands in for a value this class will not hand over in the clear.

'(encrypted)'
CREDENTIALS =

A word that makes a name a credential's: secret, api_key, otp_salt, never surname.

/secret|key|password|token|pin|salt|credential|signature/

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model, attribute) ⇒ Column

Returns a new instance of Column.

Parameters:

  • model (Class)

    an Active Record that encrypts the column.

  • attribute (Symbol)

    the attribute it encrypts.



31
32
33
34
# File 'app/models/omen/column.rb', line 31

def initialize(model, attribute)
  @model = model
  @attribute = attribute
end

Class Method Details

.allArray<Omen::Column>

Returns every encrypted column in the app, one per table and name.

Returns:

  • (Array<Omen::Column>)

    every encrypted column in the app, one per table and name.



10
11
12
13
14
15
# File 'app/models/omen/column.rb', line 10

def self.all
  Rails.application.eager_load!
  Omen.config.record.descendants.flat_map do |model|
    model.encrypted_attributes.to_a.map { |attribute| new model, attribute }
  end.uniq(&:name)
end

.of(result, connection) ⇒ Hash

An alias in the statement cannot change what Postgres says a value came from.

Parameters:

  • result (PG::Result)

    what the statement answered.

  • connection (ActiveRecord::ConnectionAdapters::AbstractAdapter)

    the one it ran on.

Returns:

  • (Hash)

    each header of the result that is an encrypted column, to that column's name.



21
22
23
24
25
26
27
# File 'app/models/omen/column.rb', line 21

def self.of(result, connection)
  sources = all.index_by { |column| column.source connection }
  result.nfields.times.each_with_object({}) do |index, into|
    found = sources[[ result.ftable(index), result.ftablecol(index) ]]
    into[result.fname index] = found.name if found
  end
end

Instance Method Details

#nameString

Returns the table and column, as Postgres names the source of a value.

Returns:

  • (String)

    the table and column, as Postgres names the source of a value.



37
# File 'app/models/omen/column.rb', line 37

def name = "#{@model.table_name}.#{@attribute}"

#read(value) ⇒ String?

Returns the plaintext, or the placeholder where there is none to be had.

Returns:

  • (String, nil)

    the plaintext, or the placeholder where there is none to be had.



49
50
51
52
53
# File 'app/models/omen/column.rb', line 49

def read(value)
  readable? ? type.deserialize(value) : HIDDEN
rescue ActiveRecord::Encryption::Errors::Decryption
  HIDDEN
end

#readable?Boolean

Returns whether a value of this column may be shown in the clear.

Returns:

  • (Boolean)

    whether a value of this column may be shown in the clear.



40
# File 'app/models/omen/column.rb', line 40

def readable? = !CREDENTIALS.match?(@attribute) && type.scheme.deterministic?

#source(connection) ⇒ Array<Integer>

Returns the table OID and column number Postgres reports for this column.

Returns:

  • (Array<Integer>)

    the table OID and column number Postgres reports for this column.



43
44
45
46
# File 'app/models/omen/column.rb', line 43

def source(connection)
  described = connection.raw_connection.exec_params probe, []
  [ described.ftable(0), described.ftablecol(0) ]
end