class OpenID::Association

An Association holds the shared secret between a relying party and an OpenID provider.

Constants

FIELD_ORDER

Attributes

assoc_type[R]
handle[R]
issued[R]
lifetime[R]
secret[R]

Public Class Methods

deserialize(serialized) click to toggle source

Load a serialized Association

# File lib/openid/association.rb, line 27
def self.deserialize(serialized)
  parsed = Util.kv_to_seq(serialized)
  parsed_fields = parsed.map{|k, v| k.to_sym}
  if parsed_fields != FIELD_ORDER
      raise ProtocolError, 'Unexpected fields in serialized association'           " (Expected #{FIELD_ORDER.inspect}, got #{parsed_fields.inspect})"
  end
  version, handle, secret64, issued_s, lifetime_s, assoc_type =
    parsed.map {|field, value| value}
  if version != '2'
    raise ProtocolError, "Attempted to deserialize unsupported version "                              "(#{parsed[0][1].inspect})"
  end

  self.new(handle,
           Util.from_base64(secret64),
           Time.at(issued_s.to_i),
           lifetime_s.to_i,
           assoc_type)
end
from_expires_in(expires_in, handle, secret, assoc_type) click to toggle source

Create an Association with an issued time of now

# File lib/openid/association.rb, line 49
def self.from_expires_in(expires_in, handle, secret, assoc_type)
  issued = Time.now
  self.new(handle, secret, issued, expires_in, assoc_type)
end
new(handle, secret, issued, lifetime, assoc_type) click to toggle source
# File lib/openid/association.rb, line 54
def initialize(handle, secret, issued, lifetime, assoc_type)
  @handle = handle
  @secret = secret
  @issued = issued
  @lifetime = lifetime
  @assoc_type = assoc_type
end

Public Instance Methods

==(other) click to toggle source
# File lib/openid/association.rb, line 136
def ==(other)
  (other.class == self.class and
   other.handle == self.handle and
   other.secret == self.secret and

   # The internals of the time objects seemed to differ
   # in an opaque way when serializing/unserializing.
   # I don't think this will be a problem.
   other.issued.to_i == self.issued.to_i and

   other.lifetime == self.lifetime and
   other.assoc_type == self.assoc_type)
end
check_message_signature(message) click to toggle source

Return whether the message's signature passes

# File lib/openid/association.rb, line 122
def check_message_signature(message)
  message_sig = message.get_arg(OPENID_NS, 'sig')
  if message_sig.nil?
    raise ProtocolError, "#{message} has no sig."
  end
  calculated_sig = get_message_signature(message)
  return CryptUtil.const_eq(calculated_sig, message_sig)
end
expires_in(now=nil) click to toggle source

The number of seconds until this association expires

# File lib/openid/association.rb, line 81
def expires_in(now=nil)
  if now.nil?
    now = Time.now.to_i
  else
    now = now.to_i
  end
  time_diff = (issued.to_i + lifetime) - now
  if time_diff < 0
    return 0
  else
    return time_diff
  end
end
get_message_signature(message) click to toggle source

Get the signature for this message

# File lib/openid/association.rb, line 132
def get_message_signature(message)
  Util.to_base64(sign(make_pairs(message)))
end
make_pairs(message) click to toggle source

Generate the list of pairs that form the signed elements of the given message

# File lib/openid/association.rb, line 111
def make_pairs(message)
  signed = message.get_arg(OPENID_NS, 'signed')
  if signed.nil?
    raise ProtocolError, 'Missing signed list'
  end
  signed_fields = signed.split(',', -1)
  data = message.to_post_args
  signed_fields.map {|field| [field, data.fetch('openid.'+field,'')] }
end
serialize() click to toggle source

Serialize the association to a form that's consistent across JanRain OpenID libraries.

# File lib/openid/association.rb, line 64
def serialize
  data = {
    :version => '2',
    :handle => handle,
    :secret => Util.to_base64(secret),
    :issued => issued.to_i.to_s,
    :lifetime => lifetime.to_i.to_s,
    :assoc_type => assoc_type,
  }

  Util.assert(data.length == FIELD_ORDER.length)

  pairs = FIELD_ORDER.map{|field| [field.to_s, data[field]]}
  return Util.seq_to_kv(pairs, true)
end
sign(pairs) click to toggle source

Generate a signature for a sequence of [key, value] pairs

# File lib/openid/association.rb, line 96
def sign(pairs)
  kv = Util.seq_to_kv(pairs)
  case assoc_type
  when 'HMAC-SHA1'
    CryptUtil.hmac_sha1(@secret, kv)
  when 'HMAC-SHA256'
    CryptUtil.hmac_sha256(@secret, kv)
  else
    raise ProtocolError, "Association has unknown type: "           "#{assoc_type.inspect}"
  end
end
sign_message(message) click to toggle source

Add a signature (and a signed list) to a message.

# File lib/openid/association.rb, line 151
def sign_message(message)
  if (message.has_key?(OPENID_NS, 'sig') or
      message.has_key?(OPENID_NS, 'signed'))
    raise ArgumentError, 'Message already has signed list or signature'
  end

  extant_handle = message.get_arg(OPENID_NS, 'assoc_handle')
  if extant_handle and extant_handle != self.handle
    raise ArgumentError, "Message has a different association handle"
  end

  signed_message = message.copy()
  signed_message.set_arg(OPENID_NS, 'assoc_handle', self.handle)
  message_keys = signed_message.to_post_args.keys()

  signed_list = []
  message_keys.each { |k|
    if k.start_with?('openid.')
      signed_list << k[7..-1]
    end
  }

  signed_list << 'signed'
  signed_list.sort!

  signed_message.set_arg(OPENID_NS, 'signed', signed_list.join(','))
  sig = get_message_signature(signed_message)
  signed_message.set_arg(OPENID_NS, 'sig', sig)
  return signed_message
end