class Ribbon::Intercom::Service::Channel

Attributes

name[R]
secret_hash_crt[R]
secret_hash_prv[R]
signing_keys[R]
store[R]
token[R]

Public Class Methods

new(store, params={}) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 18
def initialize(store, params={})
  @store = store
  @name = params[:name] or raise Errors::ChannelNameMissingError
  @token = params[:token]
  refresh(params)
end

Public Instance Methods

==(other) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 110
def ==(other)
  other.is_a?(Channel) &&
    name == other.name &&
    store == other.store &&
    token == other.token &&
    secret_crt.to_s == other.secret_crt.to_s &&
    secret_prv.to_s == other.secret_prv.to_s &&
    permissions == other.permissions
end
close() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 106
def close
  store.delete(self)
end
may(*args) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 60
def may(*args)
  (@_allowed_to ||= Hash.new(false)).merge!(
    Hash[
      args.map { |perm| [perm.to_s, true] }
    ]
  ).keys.to_set
end
may!(*args) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 68
def may!(*args)
  may(*args).tap { save }
end
may?(*perms) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 72
def may?(*perms)
  perms.all? { |perm| _may?(perm) }
end
permissions() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 76
def permissions
  @_allowed_to.keys.to_set
end
refresh(params={}) click to toggle source

Refreshes the channel. To be called by the store after obtaining a lock.

# File lib/ribbon/intercom/service/channel.rb, line 27
def refresh(params={})
  @secret_hash_crt = params[:secret_hash_crt]
  @secret_hash_prv = params[:secret_hash_prv]
  @signing_keys = params[:signing_keys] || {}

  @_allowed_to = nil
  may(*params.fetch(:may, []))
end
rotate_secret() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 36
def rotate_secret
  SecureRandom.hex(16).tap { |secret|
    @secret_hash_prv = secret_hash_crt
    @secret_hash_crt = Password.create(secret)
  }
end
rotate_secret!() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 43
def rotate_secret!
  rotate_secret.tap { save }
end
save() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 96
def save
  # Loop until unique
  unless token
    loop { break unless store.token_exists?(@token = SecureRandom.hex(4)) }
  end

  _run_validations
  store.persist(self)
end
secret_crt() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 84
def secret_crt
  @__secret_crt ||= _to_bcrypt_pw(secret_hash_crt)
end
secret_prv() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 88
def secret_prv
  @__secret_prv ||= _to_bcrypt_pw(secret_hash_prv)
end
sign(data) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 47
def sign(data)
  key_id, key = _signing_key
  "\x01" + [key_id].pack('N') + Utils::Signer.new(key).sign(data)
end
valid_secret?(secret) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 80
def valid_secret?(secret)
  !!secret && (secret_crt == secret || secret_prv == secret)
end
verify(signed_data) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 52
def verify(signed_data)
  key_id = signed_data.slice(1, 4).unpack('N').first

  if (key=_retrieve_signing_key(key_id))
    Utils::Signer.new(key).verify(signed_data[5..-1])
  end
end
with_lock(&block) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 92
def with_lock(&block)
  store.with_lock(self, &block)
end

Private Instance Methods

_add_signing_key() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 189
def _add_signing_key
  (_latest_signing_key_id.to_i + 1).tap { |key_id|
    signing_keys[key_id] = {
      key: Utils::Signer.random_key,
      timestamp: Time.now.to_i
    }
  }
end
_delete_expired_signing_keys() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 198
def _delete_expired_signing_keys
  signing_keys.reject! { |key_id| _signing_key_expired?(key_id) }
end
_latest_signing_key_id() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 139
def _latest_signing_key_id
  ((signing_keys || {}).sort_by { |k, v| v[:timestamp] }.last || []).first
end
_may?(required_perm) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 122
def _may?(required_perm)
  permissions.any? { |perm|
    regex = /\A#{Regexp.escape(perm).gsub('\*', '.*')}\z/
    regex.match(required_perm)
  }
end
_retrieve_signing_key(key_id) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 143
def _retrieve_signing_key(key_id)
  (data=_retrieve_signing_key_data(key_id)) && data[:key]
end
_retrieve_signing_key_data(key_id) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 147
def _retrieve_signing_key_data(key_id)
  if key_id && (data=signing_keys[key_id])
    data.merge(key_id: key_id)
  else
    {}
  end
end
_run_validations() click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 133
def _run_validations
  raise Errors::ChannelNameMissingError unless name
  raise Errors::ChannelTokenMissingError unless token
  raise Errors::ChannelSecretMissingError unless secret_hash_crt
end
_signing_key() click to toggle source

Retrieve the latest signing key or generate a new one if the latest has expired (older than 1 hour).

# File lib/ribbon/intercom/service/channel.rb, line 170
def _signing_key
  latest_id = _latest_signing_key_id

  # Refresh the key if the current key has less than 5 minutes to live.
  if _signing_key_ttl(latest_id) < 300
    with_lock {
      latest_id = _latest_signing_key_id

      if _signing_key_ttl(latest_id) < 300
        latest_id = _add_signing_key
        _delete_expired_signing_keys
        save # Need to persist changes to signing keys.
      end
    }
  end

  [latest_id, _retrieve_signing_key(latest_id)]
end
_signing_key_expired?(key_id) click to toggle source

Returns whether the key is expired. Optionally, an offset may be specified to expire the key earlier or later.

# File lib/ribbon/intercom/service/channel.rb, line 158
def _signing_key_expired?(key_id)
  _signing_key_ttl(key_id) <= 0
end
_signing_key_ttl(key_id, ttl=3600) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 162
def _signing_key_ttl(key_id, ttl=3600)
  timestamp = _retrieve_signing_key_data(key_id)[:timestamp]
  (timestamp && (ttl - (Time.now.to_i - timestamp))).to_i
end
_to_bcrypt_pw(pw) click to toggle source
# File lib/ribbon/intercom/service/channel.rb, line 129
def _to_bcrypt_pw(pw)
  Password.new(pw) if pw && !pw.empty?
end