Class: Familia::Encryption::Registry

Inherits:
Object
  • Object
show all
Defined in:
lib/familia/encryption/registry.rb

Overview

Registry pattern for managing encryption providers

Class Method Summary collapse

Class Method Details

.available_algorithmsObject



69
70
71
# File 'lib/familia/encryption/registry.rb', line 69

def available_algorithms
  providers.keys
end

.default_providerObject



57
58
59
60
61
62
63
# File 'lib/familia/encryption/registry.rb', line 57

def default_provider
  # Select provider with highest priority
  @default_provider ||= begin
    available = providers.values.select(&:available?)
    available.max_by(&:priority)&.new
  end
end

.get(algorithm) ⇒ Object

Raises:



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/familia/encryption/registry.rb', line 20

def get(algorithm)
  provider_class = providers[algorithm]
  return provider_class.new if provider_class

  # Not registered. `register` only stores providers whose runtime
  # dependency is present (available? == true), so distinguish a
  # genuinely unknown algorithm from a known one whose provider isn't
  # installed on this node. The latter is the common misstep when
  # pinning `encrypted_field ..., algorithm:` ahead of a fleet rollout
  # (e.g. pinning xchacha20poly1305 before rbnacl/libsodium is
  # deployed), so point at the real fix instead of implying a typo.
  known = known_providers.find { |klass| algorithm == klass::ALGORITHM }
  if known
    # get is on both the write (encrypt) and read (decrypt) paths --
    # Manager#decrypt resolves the provider from the stored envelope's
    # own algorithm. Installing the dependency is the fix for BOTH;
    # pinning to another algorithm is a write-time workaround only and
    # cannot decrypt ciphertext already written under this one, so the
    # message frames pinning as write-only rather than a general fix.
    # Each provider declares its own dependency (via .dependency_hint),
    # so this generic path stays accurate as providers are added without
    # naming any one library here.
    dependency = known.dependency_hint
    requirement = dependency ? " (requires #{dependency})" : ''
    raise EncryptionError,
          "Algorithm #{algorithm.inspect} is known but its provider " \
          "(#{known.name}) is not available on this node -- its " \
          "runtime dependency is missing#{requirement}. Install the " \
          'dependency to read or write this algorithm. (For writes you ' \
          'may instead pin the field to an available algorithm ' \
          "(#{available_algorithms.inspect}), but that cannot decrypt " \
          'ciphertext already written with this one.)'
  end

  raise EncryptionError, "Unsupported algorithm: #{algorithm}"
end

.known_providersObject

Every provider class Familia knows how to register, regardless of whether its runtime dependency is available on this node. providers holds only the subset that passed available?; this is the full set, and the single source of truth shared by setup! (which registers the available ones) and get (which uses it to tell an unknown algorithm apart from a known-but-unavailable one).



79
80
81
82
83
84
85
# File 'lib/familia/encryption/registry.rb', line 79

def known_providers
  [
    Providers::XChaCha20Poly1305Provider,
    Providers::AESGCMProvider,
    # Future: Providers::ChaCha20Poly1305Provider
  ]
end

.providersObject



10
11
12
# File 'lib/familia/encryption/registry.rb', line 10

def providers
  @providers ||= {}
end

.register(provider_class) ⇒ Object



14
15
16
17
18
# File 'lib/familia/encryption/registry.rb', line 14

def register(provider_class)
  return unless provider_class.available?

  providers[provider_class::ALGORITHM] = provider_class
end

.reset_default_provider!Object



65
66
67
# File 'lib/familia/encryption/registry.rb', line 65

def reset_default_provider!
  @default_provider = nil
end

.setup!Object

Auto-register known providers



88
89
90
# File 'lib/familia/encryption/registry.rb', line 88

def setup!
  known_providers.each { |provider_class| register(provider_class) }
end