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 =
i[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.



55
56
57
58
59
60
61
# File 'lib/openid/association.rb', line 55

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.



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

def assoc_type
  @assoc_type
end

#handleObject (readonly)

Returns the value of attribute handle.



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

def handle
  @handle
end

#issuedObject (readonly)

Returns the value of attribute issued.



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

def issued
  @issued
end

#lifetimeObject (readonly)

Returns the value of attribute lifetime.



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

def lifetime
  @lifetime
end

#secretObject (readonly)

Returns the value of attribute secret.



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

def secret
  @secret
end

Class Method Details

.deserialize(serialized) ⇒ Object

Load a serialized Association



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/openid/association.rb', line 26

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

  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



50
51
52
53
# File 'lib/openid/association.rb', line 50

def self.from_expires_in(expires_in, handle, secret, assoc_type)
  issued = Time.now
  new(handle, secret, issued, expires_in, assoc_type)
end

Instance Method Details

#==(other) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/openid/association.rb', line 133

def ==(other)
  (other.class == self.class and
   other.handle == handle and
   other.secret == 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 == issued.to_i and

   other.lifetime == lifetime and
   other.assoc_type == assoc_type)
end

#check_message_signature(message) ⇒ Object

Return whether the message’s signature passes

Raises:



120
121
122
123
124
125
126
# File 'lib/openid/association.rb', line 120

def check_message_signature(message)
  message_sig = message.get_arg(OPENID_NS, "sig")
  raise ProtocolError, "#{message} has no sig." if message_sig.nil?

  calculated_sig = get_message_signature(message)
  CryptUtil.const_eq(calculated_sig, message_sig)
end

#expires_in(now = nil) ⇒ Object

The number of seconds until this association expires



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/openid/association.rb', line 82

def expires_in(now = nil)
  now = if now.nil?
    Time.now.to_i
  else
    now.to_i
  end
  time_diff = (issued.to_i + lifetime) - now
  return 0 if time_diff < 0

  time_diff
end

#get_message_signature(message) ⇒ Object

Get the signature for this message



129
130
131
# File 'lib/openid/association.rb', line 129

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

Raises:



110
111
112
113
114
115
116
117
# File 'lib/openid/association.rb', line 110

def make_pairs(message)
  signed = message.get_arg(OPENID_NS, "signed")
  raise ProtocolError, "Missing signed list" if signed.nil?

  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.



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/openid/association.rb', line 65

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.truthy_assert(data.length == FIELD_ORDER.length)

  pairs = FIELD_ORDER.map { |field| [field.to_s, data[field]] }
  Util.seq_to_kv(pairs, true)
end

#sign(pairs) ⇒ Object

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



95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/openid/association.rb', line 95

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.

Raises:

  • (ArgumentError)


148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/openid/association.rb', line 148

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")
  raise ArgumentError, "Message has a different association handle" if extant_handle and extant_handle != handle

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

  signed_list = []
  message_keys.each do |k|
    signed_list << k[7..-1] if k.start_with?("openid.")
  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)
  signed_message
end