module Darrrr::Provider

Constants

MAX_RECOVERY_PROVIDER_CACHE_LENGTH
RECOVERY_PROVIDER_CACHE_LENGTH
REQUIRED_CRYPTO_OPS

Attributes

this[RW]

Public Class Methods

configure(&block) click to toggle source
# File lib/darrrr/provider.rb, line 16
def configure(&block)
  raise ArgumentError, "Block required to configure #{self.name}" unless block_given?
  raise ProviderConfigError, "#{self.name} already configured" if self.this
  self.this = self.new.tap { |provider| provider.instance_eval(&block).freeze }
  self.this.privacy_policy = Darrrr.privacy_policy
  self.this.icon_152px = Darrrr.icon_152px
  self.this.issuer = Darrrr.authority
end
included(base) click to toggle source
# File lib/darrrr/provider.rb, line 10
def self.included(base)
  base.instance_eval do
    # this represents the account/recovery provider on this web app
    class << self
      attr_accessor :this

      def configure(&block)
        raise ArgumentError, "Block required to configure #{self.name}" unless block_given?
        raise ProviderConfigError, "#{self.name} already configured" if self.this
        self.this = self.new.tap { |provider| provider.instance_eval(&block).freeze }
        self.this.privacy_policy = Darrrr.privacy_policy
        self.this.icon_152px = Darrrr.icon_152px
        self.this.issuer = Darrrr.authority
      end
    end
  end
end
new(provider_origin = nil, attrs: nil) click to toggle source
# File lib/darrrr/provider.rb, line 28
def initialize(provider_origin = nil, attrs: nil)
  self.issuer = provider_origin
  load(attrs) if attrs
end

Public Instance Methods

custom_encryptor=(encryptor) click to toggle source

Overrides the global `encryptor` API to use

encryptor: a class/module that responds to all REQUIRED_CRYPTO_OPS.

# File lib/darrrr/provider.rb, line 42
def custom_encryptor=(encryptor)
  if valid_encryptor?(encryptor)
    @encryptor = encryptor
  else
    raise ArgumentError, "custom encryption class must respond to all of #{REQUIRED_CRYPTO_OPS}"
  end
end
encryptor() click to toggle source

Returns the crypto API to be used. A thread local instance overrides the globally configured value which overrides the default encryptor.

# File lib/darrrr/provider.rb, line 35
def encryptor
  Thread.current[encryptor_key] || @encryptor || DefaultEncryptor
end
load(attrs = nil) click to toggle source

Lazily loads attributes if attrs is nil. It makes an http call to the recovery provider's well-known config location and caches the response if it's valid json.

attrs: optional way of building the provider without making an http call.

# File lib/darrrr/provider.rb, line 71
def load(attrs = nil)
  body = attrs || fetch_config!
  set_attrs!(body)
  self
end
with_encryptor(encryptor) { || ... } click to toggle source
# File lib/darrrr/provider.rb, line 50
def with_encryptor(encryptor)
  raise ArgumentError, "A block must be supplied" unless block_given?
  unless valid_encryptor?(encryptor)
    raise ArgumentError, "custom encryption class must respond to all of #{REQUIRED_CRYPTO_OPS}"
  end

  Thread.current[encryptor_key] = encryptor
  yield
ensure
  Thread.current[encryptor_key] = nil
end

Private Instance Methods

cache_config(response) click to toggle source
# File lib/darrrr/provider.rb, line 87
        def cache_config(response)
  match = /max-age=(\d+)/.match(response.headers["cache-control"])
  cache_age = if match
    [match[1].to_i, MAX_RECOVERY_PROVIDER_CACHE_LENGTH].min
  else
    RECOVERY_PROVIDER_CACHE_LENGTH
  end
  Darrrr.cache.try(:set, cache_key, response.body, cache_age)
end
cache_key() click to toggle source
# File lib/darrrr/provider.rb, line 97
        def cache_key
  "recovery_provider_config:#{self.origin}:configuration"
end
errors() click to toggle source
# File lib/darrrr/provider.rb, line 129
        def errors
  errors = []
  self.class::REQUIRED_FIELDS.each do |field|
    unless self.instance_variable_get("@#{field}")
      errors << "#{field} not set"
    end
  end

  self.class::URL_FIELDS.each do |field|
    begin
      uri = Addressable::URI.parse(self.instance_variable_get("@#{field}"))
      if !Darrrr.allow_unsafe_urls && uri.try(:scheme) != "https"
        errors << "#{field} must be an https URL"
      end
    rescue Addressable::URI::InvalidURIError
      errors << "#{field} must be a valid URL"
    end
  end

  if self.is_a? RecoveryProvider
    unless self.token_max_size.to_i > 0
      errors << "token max size must be an integer"
    end
  end

  unless self.unseal_keys.try(:any?)
    errors << "No public key provided"
  end

  errors
end
faraday() click to toggle source
# File lib/darrrr/provider.rb, line 77
        def faraday
  Faraday.new do |f|
    if Darrrr.faraday_config_callback
      Darrrr.faraday_config_callback.call(f)
    else
      f.adapter(Faraday.default_adapter)
    end
  end
end
fetch_config!() click to toggle source
# File lib/darrrr/provider.rb, line 101
        def fetch_config!
  unless body = Darrrr.cache.try(:get, cache_key)
    response = faraday.get([self.origin, Darrrr::WELL_KNOWN_CONFIG_PATH].join("/"))
    if response.success?
      cache_config(response)
    else
      raise ProviderConfigError.new("Unable to retrieve recovery provider config for #{self.origin}: #{response.status}: #{response.body[0..100]}")
    end

    body = response.body
  end

  JSON.parse(body)
rescue ::JSON::ParserError
  raise ProviderConfigError.new("Unable to parse recovery provider config for #{self.origin}:#{body[0..100]}")
end
set_attrs!(context) click to toggle source
# File lib/darrrr/provider.rb, line 118
        def set_attrs!(context)
  self.class::REQUIRED_FIELDS.each do |attr|
    value = context[attr.to_s.tr("_", "-")]
    self.instance_variable_set("@#{attr}", value)
  end

  if errors.any?
    raise ProviderConfigError.new("Unable to parse recovery provider config for #{self.origin}: #{errors.join(", ")}")
  end
end
valid_encryptor?(encryptor) click to toggle source
# File lib/darrrr/provider.rb, line 62
        def valid_encryptor?(encryptor)
  REQUIRED_CRYPTO_OPS.all? { |m| encryptor.respond_to?(m) }
end