Class: OpenID::Association

Inherits:
Object
  • Object
show all
Defined in:
lib/openid/association.rb

Overview

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

Constant Summary collapse

FIELD_ORDER =
[:version, :handle, :secret, :issued, :lifetime, :assoc_type,]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(handle, secret, issued, lifetime, assoc_type) ⇒ Association

Returns a new instance of Association.



54
55
56
57
58
59
60
# 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

Instance Attribute Details

#assoc_typeObject (readonly)

Returns the value of attribute assoc_type.



21
22
23
# File 'lib/openid/association.rb', line 21

def assoc_type
  @assoc_type
end

#handleObject (readonly)

Returns the value of attribute handle.



21
22
23
# File 'lib/openid/association.rb', line 21

def handle
  @handle
end

#issuedObject (readonly)

Returns the value of attribute issued.



21
22
23
# File 'lib/openid/association.rb', line 21

def issued
  @issued
end

#lifetimeObject (readonly)

Returns the value of attribute lifetime.



21
22
23
# File 'lib/openid/association.rb', line 21

def lifetime
  @lifetime
end

#secretObject (readonly)

Returns the value of attribute secret.



21
22
23
# File 'lib/openid/association.rb', line 21

def secret
  @secret
end

Class Method Details

.deserialize(serialized) ⇒ Object

Load a serialized Association



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# 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) ⇒ Object

Create an Association with an issued time of now



49
50
51
52
# 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

Instance Method Details

#==(other) ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
# 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) ⇒ Object

Return whether the message’s signature passes



122
123
124
125
126
127
128
129
# 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) ⇒ Object

The number of seconds until this association expires



81
82
83
84
85
86
87
88
89
90
91
92
93
# 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) ⇒ Object

Get the signature for this message



132
133
134
# File 'lib/openid/association.rb', line 132

def get_message_signature(message)
  Util.to_base64(sign(make_pairs(message)))
end

#make_pairs(message) ⇒ Object

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



111
112
113
114
115
116
117
118
119
# 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

#serializeObject

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



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# 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) ⇒ Object

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



96
97
98
99
100
101
102
103
104
105
106
107
# 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) ⇒ Object

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



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# 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