ruby-openid2 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +54 -0
- data/LICENSE.txt +210 -0
- data/README.md +81 -0
- data/SECURITY.md +15 -0
- data/lib/hmac/hmac.rb +110 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid/association.rb +246 -0
- data/lib/openid/consumer/associationmanager.rb +354 -0
- data/lib/openid/consumer/checkid_request.rb +179 -0
- data/lib/openid/consumer/discovery.rb +516 -0
- data/lib/openid/consumer/discovery_manager.rb +144 -0
- data/lib/openid/consumer/html_parse.rb +142 -0
- data/lib/openid/consumer/idres.rb +513 -0
- data/lib/openid/consumer/responses.rb +147 -0
- data/lib/openid/consumer/session.rb +36 -0
- data/lib/openid/consumer.rb +406 -0
- data/lib/openid/cryptutil.rb +112 -0
- data/lib/openid/dh.rb +84 -0
- data/lib/openid/extension.rb +38 -0
- data/lib/openid/extensions/ax.rb +552 -0
- data/lib/openid/extensions/oauth.rb +88 -0
- data/lib/openid/extensions/pape.rb +170 -0
- data/lib/openid/extensions/sreg.rb +268 -0
- data/lib/openid/extensions/ui.rb +49 -0
- data/lib/openid/fetchers.rb +277 -0
- data/lib/openid/kvform.rb +113 -0
- data/lib/openid/kvpost.rb +62 -0
- data/lib/openid/message.rb +555 -0
- data/lib/openid/protocolerror.rb +7 -0
- data/lib/openid/server.rb +1571 -0
- data/lib/openid/store/filesystem.rb +260 -0
- data/lib/openid/store/interface.rb +73 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +79 -0
- data/lib/openid/store/nonce.rb +72 -0
- data/lib/openid/trustroot.rb +597 -0
- data/lib/openid/urinorm.rb +72 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +5 -0
- data/lib/openid/yadis/accept.rb +141 -0
- data/lib/openid/yadis/constants.rb +16 -0
- data/lib/openid/yadis/discovery.rb +151 -0
- data/lib/openid/yadis/filters.rb +192 -0
- data/lib/openid/yadis/htmltokenizer.rb +290 -0
- data/lib/openid/yadis/parsehtml.rb +50 -0
- data/lib/openid/yadis/services.rb +44 -0
- data/lib/openid/yadis/xrds.rb +160 -0
- data/lib/openid/yadis/xri.rb +86 -0
- data/lib/openid/yadis/xrires.rb +87 -0
- data/lib/openid.rb +27 -0
- data/lib/ruby-openid.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +331 -0
- metadata.gz.sig +0 -0
data/lib/openid/dh.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require_relative "util"
|
2
|
+
require_relative "cryptutil"
|
3
|
+
|
4
|
+
module OpenID
|
5
|
+
# Encapsulates a Diffie-Hellman key exchange. This class is used
|
6
|
+
# internally by both the consumer and server objects.
|
7
|
+
#
|
8
|
+
# Read more about Diffie-Hellman on wikipedia:
|
9
|
+
# http://en.wikipedia.org/wiki/Diffie-Hellman
|
10
|
+
|
11
|
+
class DiffieHellman
|
12
|
+
# From the OpenID specification
|
13
|
+
@@default_mod = 155_172_898_181_473_697_471_232_257_763_715_539_915_724_801_966_915_404_479_707_795_314_057_629_378_541_917_580_651_227_423_698_188_993_727_816_152_646_631_438_561_595_825_688_188_889_951_272_158_842_675_419_950_341_258_706_556_549_803_580_104_870_537_681_476_726_513_255_747_040_765_857_479_291_291_572_334_510_643_245_094_715_007_229_621_094_194_349_783_925_984_760_375_594_985_848_253_359_305_585_439_638_443
|
14
|
+
@@default_gen = 2
|
15
|
+
|
16
|
+
attr_reader :modulus, :generator, :public
|
17
|
+
|
18
|
+
# A new DiffieHellman object, using the modulus and generator from
|
19
|
+
# the OpenID specification
|
20
|
+
def self.from_defaults
|
21
|
+
DiffieHellman.new(@@default_mod, @@default_gen)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(modulus = nil, generator = nil, priv = nil)
|
25
|
+
@modulus = modulus.nil? ? @@default_mod : modulus
|
26
|
+
@generator = generator.nil? ? @@default_gen : generator
|
27
|
+
set_private(priv.nil? ? OpenID::CryptUtil.rand(@modulus - 2) + 1 : priv)
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_shared_secret(composite)
|
31
|
+
DiffieHellman.powermod(composite, @private, @modulus)
|
32
|
+
end
|
33
|
+
|
34
|
+
def xor_secret(algorithm, composite, secret)
|
35
|
+
dh_shared = get_shared_secret(composite)
|
36
|
+
packed_dh_shared = OpenID::CryptUtil.num_to_binary(dh_shared)
|
37
|
+
hashed_dh_shared = algorithm.call(packed_dh_shared)
|
38
|
+
DiffieHellman.strxor(secret, hashed_dh_shared)
|
39
|
+
end
|
40
|
+
|
41
|
+
def using_default_values?
|
42
|
+
@generator == @@default_gen && @modulus == @@default_mod
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def set_private(priv)
|
48
|
+
@private = priv
|
49
|
+
@public = DiffieHellman.powermod(@generator, @private, @modulus)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.strxor(s, t)
|
53
|
+
if s.length != t.length
|
54
|
+
raise ArgumentError, "strxor: lengths don't match. " +
|
55
|
+
"Inputs were #{s.inspect} and #{t.inspect}"
|
56
|
+
end
|
57
|
+
|
58
|
+
if String.method_defined?(:bytes)
|
59
|
+
s.bytes.to_a.zip(t.bytes.to_a).map { |sb, tb| sb ^ tb }.pack("C*")
|
60
|
+
else
|
61
|
+
indices = 0...(s.length)
|
62
|
+
chrs = indices.collect { |i| (s[i] ^ t[i]).chr }
|
63
|
+
chrs.join("")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# This code is taken from this post:
|
68
|
+
# <http://blade.nagaokaut.ac.jp/cgi-bin/scat.\rb/ruby/ruby-talk/19098>
|
69
|
+
# by Eric Lee Green.
|
70
|
+
def self.powermod(x, n, q)
|
71
|
+
counter = 0
|
72
|
+
n_p = n
|
73
|
+
y_p = 1
|
74
|
+
z_p = x
|
75
|
+
while n_p != 0
|
76
|
+
y_p = (y_p * z_p) % q if n_p[0] == 1
|
77
|
+
n_p >>= 1
|
78
|
+
z_p = (z_p * z_p) % q
|
79
|
+
counter += 1
|
80
|
+
end
|
81
|
+
y_p
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative "message"
|
2
|
+
|
3
|
+
module OpenID
|
4
|
+
# An interface for OpenID extensions.
|
5
|
+
class Extension < Object
|
6
|
+
def initialize
|
7
|
+
@ns_uri = nil
|
8
|
+
@ns_alias = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
# Get the string arguments that should be added to an OpenID
|
12
|
+
# message for this extension.
|
13
|
+
def get_extension_args
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add the arguments from this extension to the provided
|
18
|
+
# message, or create a new message containing only those
|
19
|
+
# arguments. Returns the message with added extension args.
|
20
|
+
def to_message(message = nil)
|
21
|
+
if message.nil?
|
22
|
+
# warnings.warn('Passing None to Extension.toMessage is deprecated. '
|
23
|
+
# 'Creating a message assuming you want OpenID 2.',
|
24
|
+
# DeprecationWarning, stacklevel=2)
|
25
|
+
Message.new(OPENID2_NS)
|
26
|
+
end
|
27
|
+
message = Message.new if message.nil?
|
28
|
+
|
29
|
+
implicit = message.is_openid1
|
30
|
+
|
31
|
+
message.namespaces.add_alias(@ns_uri, @ns_alias, implicit)
|
32
|
+
# XXX python ignores keyerror if m.ns.getAlias(uri) == alias
|
33
|
+
|
34
|
+
message.update_args(@ns_uri, get_extension_args)
|
35
|
+
message
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,552 @@
|
|
1
|
+
# Implements the OpenID attribute exchange specification, version 1.0
|
2
|
+
|
3
|
+
require_relative "../extension"
|
4
|
+
require_relative "../trustroot"
|
5
|
+
require_relative "../message"
|
6
|
+
|
7
|
+
module OpenID
|
8
|
+
module AX
|
9
|
+
UNLIMITED_VALUES = "unlimited"
|
10
|
+
MINIMUM_SUPPORTED_ALIAS_LENGTH = 32
|
11
|
+
|
12
|
+
# check alias for invalid characters, raise AXError if found
|
13
|
+
def self.check_alias(name)
|
14
|
+
return unless /(,|\.)/.match?(name)
|
15
|
+
|
16
|
+
raise Error, "Alias #{name.inspect} must not contain a " \
|
17
|
+
"comma or period."
|
18
|
+
end
|
19
|
+
|
20
|
+
# Raised when data does not comply with AX 1.0 specification
|
21
|
+
class Error < ArgumentError
|
22
|
+
end
|
23
|
+
|
24
|
+
# Abstract class containing common code for attribute exchange messages
|
25
|
+
class AXMessage < Extension
|
26
|
+
attr_accessor :ns_alias, :mode, :ns_uri
|
27
|
+
|
28
|
+
NS_URI = "http://openid.net/srv/ax/1.0"
|
29
|
+
|
30
|
+
begin
|
31
|
+
Message.register_namespace_alias(NS_URI, "ax")
|
32
|
+
rescue NamespaceAliasRegistrationError => e
|
33
|
+
Util.log(e)
|
34
|
+
end
|
35
|
+
|
36
|
+
def initialize
|
37
|
+
@ns_alias = "ax"
|
38
|
+
@ns_uri = NS_URI
|
39
|
+
@mode = nil
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Raise an exception if the mode in the attribute exchange
|
45
|
+
# arguments does not match what is expected for this class.
|
46
|
+
def check_mode(ax_args)
|
47
|
+
actual_mode = ax_args ? ax_args["mode"] : nil
|
48
|
+
return unless actual_mode != @mode
|
49
|
+
|
50
|
+
raise Error, "Expected mode #{mode.inspect}, got #{actual_mode.inspect}"
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_args
|
54
|
+
{"mode" => @mode}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Represents a single attribute in an attribute exchange
|
59
|
+
# request. This should be added to an Request object in order to
|
60
|
+
# request the attribute.
|
61
|
+
#
|
62
|
+
# @ivar required: Whether the attribute will be marked as required
|
63
|
+
# when presented to the subject of the attribute exchange
|
64
|
+
# request.
|
65
|
+
# @type required: bool
|
66
|
+
#
|
67
|
+
# @ivar count: How many values of this type to request from the
|
68
|
+
# subject. Defaults to one.
|
69
|
+
# @type count: int
|
70
|
+
#
|
71
|
+
# @ivar type_uri: The identifier that determines what the attribute
|
72
|
+
# represents and how it is serialized. For example, one type URI
|
73
|
+
# representing dates could represent a Unix timestamp in base 10
|
74
|
+
# and another could represent a human-readable string.
|
75
|
+
# @type type_uri: str
|
76
|
+
#
|
77
|
+
# @ivar ns_alias: The name that should be given to this alias in the
|
78
|
+
# request. If it is not supplied, a generic name will be
|
79
|
+
# assigned. For example, if you want to call a Unix timestamp
|
80
|
+
# value 'tstamp', set its alias to that value. If two attributes
|
81
|
+
# in the same message request to use the same alias, the request
|
82
|
+
# will fail to be generated.
|
83
|
+
# @type alias: str or NoneType
|
84
|
+
class AttrInfo < Object
|
85
|
+
attr_reader :type_uri, :count, :ns_alias
|
86
|
+
attr_accessor :required
|
87
|
+
|
88
|
+
def initialize(type_uri, ns_alias = nil, required = false, count = 1)
|
89
|
+
@type_uri = type_uri
|
90
|
+
@count = count
|
91
|
+
@required = required
|
92
|
+
@ns_alias = ns_alias
|
93
|
+
end
|
94
|
+
|
95
|
+
def wants_unlimited_values?
|
96
|
+
@count == UNLIMITED_VALUES
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# Given a namespace mapping and a string containing a
|
101
|
+
# comma-separated list of namespace aliases, return a list of type
|
102
|
+
# URIs that correspond to those aliases.
|
103
|
+
# namespace_map: OpenID::NamespaceMap
|
104
|
+
def self.to_type_uris(namespace_map, alias_list_s)
|
105
|
+
return [] if alias_list_s.nil?
|
106
|
+
|
107
|
+
alias_list_s.split(",").inject([]) do |uris, name|
|
108
|
+
type_uri = namespace_map.get_namespace_uri(name)
|
109
|
+
raise IndexError, "No type defined for attribute name #{name.inspect}" if type_uri.nil?
|
110
|
+
|
111
|
+
uris << type_uri
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# An attribute exchange 'fetch_request' message. This message is
|
116
|
+
# sent by a relying party when it wishes to obtain attributes about
|
117
|
+
# the subject of an OpenID authentication request.
|
118
|
+
class FetchRequest < AXMessage
|
119
|
+
attr_reader :requested_attributes
|
120
|
+
attr_accessor :update_url
|
121
|
+
|
122
|
+
MODE = "fetch_request"
|
123
|
+
|
124
|
+
def initialize(update_url = nil)
|
125
|
+
super()
|
126
|
+
@mode = MODE
|
127
|
+
@requested_attributes = {}
|
128
|
+
@update_url = update_url
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add an attribute to this attribute exchange request.
|
132
|
+
# attribute: AttrInfo, the attribute being requested
|
133
|
+
# Raises IndexError if the requested attribute is already present
|
134
|
+
# in this request.
|
135
|
+
def add(attribute)
|
136
|
+
if @requested_attributes[attribute.type_uri]
|
137
|
+
raise IndexError, "The attribute #{attribute.type_uri} has already been requested"
|
138
|
+
end
|
139
|
+
|
140
|
+
@requested_attributes[attribute.type_uri] = attribute
|
141
|
+
end
|
142
|
+
|
143
|
+
# Get the serialized form of this attribute fetch request.
|
144
|
+
# returns a hash of the arguments
|
145
|
+
def get_extension_args
|
146
|
+
aliases = NamespaceMap.new
|
147
|
+
required = []
|
148
|
+
if_available = []
|
149
|
+
ax_args = new_args
|
150
|
+
@requested_attributes.each do |type_uri, attribute|
|
151
|
+
name = if attribute.ns_alias
|
152
|
+
aliases.add_alias(type_uri, attribute.ns_alias)
|
153
|
+
else
|
154
|
+
aliases.add(type_uri)
|
155
|
+
end
|
156
|
+
if attribute.required
|
157
|
+
required << name
|
158
|
+
else
|
159
|
+
if_available << name
|
160
|
+
end
|
161
|
+
ax_args["count.#{name}"] = attribute.count.to_s if attribute.count != 1
|
162
|
+
ax_args["type.#{name}"] = type_uri
|
163
|
+
end
|
164
|
+
|
165
|
+
ax_args["required"] = required.join(",") unless required.empty?
|
166
|
+
ax_args["if_available"] = if_available.join(",") unless if_available.empty?
|
167
|
+
ax_args
|
168
|
+
end
|
169
|
+
|
170
|
+
# Get the type URIs for all attributes that have been marked
|
171
|
+
# as required.
|
172
|
+
def get_required_attrs
|
173
|
+
@requested_attributes.inject([]) do |required, (type_uri, attribute)|
|
174
|
+
if attribute.required
|
175
|
+
required << type_uri
|
176
|
+
else
|
177
|
+
required
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# Extract a FetchRequest from an OpenID message
|
183
|
+
# message: OpenID::Message
|
184
|
+
# return a FetchRequest or nil if AX arguments are not present
|
185
|
+
def self.from_openid_request(oidreq)
|
186
|
+
message = oidreq.message
|
187
|
+
ax_args = message.get_args(NS_URI)
|
188
|
+
return if ax_args == {} or ax_args["mode"] != MODE
|
189
|
+
|
190
|
+
req = new
|
191
|
+
req.parse_extension_args(ax_args)
|
192
|
+
|
193
|
+
if req.update_url
|
194
|
+
realm = message.get_arg(
|
195
|
+
OPENID_NS,
|
196
|
+
"realm",
|
197
|
+
message.get_arg(OPENID_NS, "return_to"),
|
198
|
+
)
|
199
|
+
if realm.nil? or realm.empty?
|
200
|
+
raise Error, "Cannot validate update_url #{req.update_url.inspect} against absent realm"
|
201
|
+
end
|
202
|
+
|
203
|
+
tr = TrustRoot::TrustRoot.parse(realm)
|
204
|
+
unless tr.validate_url(req.update_url)
|
205
|
+
raise Error, "Update URL #{req.update_url.inspect} failed validation against realm #{realm.inspect}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
req
|
210
|
+
end
|
211
|
+
|
212
|
+
def parse_extension_args(ax_args)
|
213
|
+
check_mode(ax_args)
|
214
|
+
|
215
|
+
aliases = NamespaceMap.new
|
216
|
+
|
217
|
+
ax_args.each do |k, v|
|
218
|
+
next unless k.index("type.") == 0
|
219
|
+
|
220
|
+
name = k[5..-1]
|
221
|
+
type_uri = v
|
222
|
+
aliases.add_alias(type_uri, name)
|
223
|
+
|
224
|
+
count_key = "count." + name
|
225
|
+
count_s = ax_args[count_key]
|
226
|
+
count = 1
|
227
|
+
if count_s
|
228
|
+
if count_s == UNLIMITED_VALUES
|
229
|
+
count = count_s
|
230
|
+
else
|
231
|
+
count = count_s.to_i
|
232
|
+
raise Error, "Invalid value for count #{count_key.inspect}: #{count_s.inspect}" if count <= 0
|
233
|
+
end
|
234
|
+
end
|
235
|
+
add(AttrInfo.new(type_uri, name, false, count))
|
236
|
+
end
|
237
|
+
|
238
|
+
required = AX.to_type_uris(aliases, ax_args["required"])
|
239
|
+
required.each do |type_uri|
|
240
|
+
@requested_attributes[type_uri].required = true
|
241
|
+
end
|
242
|
+
if_available = AX.to_type_uris(aliases, ax_args["if_available"])
|
243
|
+
all_type_uris = required + if_available
|
244
|
+
|
245
|
+
aliases.namespace_uris.each do |type_uri|
|
246
|
+
unless all_type_uris.member?(type_uri)
|
247
|
+
raise Error,
|
248
|
+
"Type URI #{type_uri.inspect} was in the request but not present in 'required' or 'if_available'"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
@update_url = ax_args["update_url"]
|
252
|
+
end
|
253
|
+
|
254
|
+
# return the list of AttrInfo objects contained in the FetchRequest
|
255
|
+
def attributes
|
256
|
+
@requested_attributes.values
|
257
|
+
end
|
258
|
+
|
259
|
+
# return the list of requested attribute type URIs
|
260
|
+
def requested_types
|
261
|
+
@requested_attributes.keys
|
262
|
+
end
|
263
|
+
|
264
|
+
def member?(type_uri)
|
265
|
+
!@requested_attributes[type_uri].nil?
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
# Abstract class that implements a message that has attribute
|
270
|
+
# keys and values. It contains the common code between
|
271
|
+
# fetch_response and store_request.
|
272
|
+
class KeyValueMessage < AXMessage
|
273
|
+
attr_reader :data
|
274
|
+
|
275
|
+
def initialize
|
276
|
+
super
|
277
|
+
@mode = nil
|
278
|
+
@data = Hash.new { |hash, key| hash[key] = [] }
|
279
|
+
end
|
280
|
+
|
281
|
+
# Add a single value for the given attribute type to the
|
282
|
+
# message. If there are already values specified for this type,
|
283
|
+
# this value will be sent in addition to the values already
|
284
|
+
# specified.
|
285
|
+
def add_value(type_uri, value)
|
286
|
+
@data[type_uri] = @data[type_uri] << value
|
287
|
+
end
|
288
|
+
|
289
|
+
# Set the values for the given attribute type. This replaces
|
290
|
+
# any values that have already been set for this attribute.
|
291
|
+
def set_values(type_uri, values)
|
292
|
+
@data[type_uri] = values
|
293
|
+
end
|
294
|
+
|
295
|
+
# Get the extension arguments for the key/value pairs
|
296
|
+
# contained in this message.
|
297
|
+
def _get_extension_kv_args(aliases = nil)
|
298
|
+
aliases = NamespaceMap.new if aliases.nil?
|
299
|
+
|
300
|
+
ax_args = new_args
|
301
|
+
|
302
|
+
@data.each do |type_uri, values|
|
303
|
+
name = aliases.add(type_uri)
|
304
|
+
ax_args["type." + name] = type_uri
|
305
|
+
if values.size > 1
|
306
|
+
ax_args["count." + name] = values.size.to_s
|
307
|
+
|
308
|
+
values.each_with_index do |value, i|
|
309
|
+
key = "value.#{name}.#{i + 1}"
|
310
|
+
ax_args[key] = value
|
311
|
+
end
|
312
|
+
# for attributes with only a single value, use a
|
313
|
+
# nice shortcut to only show the value w/o the count
|
314
|
+
else
|
315
|
+
values.each do |value|
|
316
|
+
key = "value.#{name}"
|
317
|
+
ax_args[key] = value
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
ax_args
|
322
|
+
end
|
323
|
+
|
324
|
+
# Parse attribute exchange key/value arguments into this object.
|
325
|
+
|
326
|
+
def parse_extension_args(ax_args)
|
327
|
+
check_mode(ax_args)
|
328
|
+
aliases = NamespaceMap.new
|
329
|
+
|
330
|
+
ax_args.each do |k, v|
|
331
|
+
next unless k.index("type.") == 0
|
332
|
+
|
333
|
+
type_uri = v
|
334
|
+
name = k[5..-1]
|
335
|
+
|
336
|
+
AX.check_alias(name)
|
337
|
+
aliases.add_alias(type_uri, name)
|
338
|
+
end
|
339
|
+
|
340
|
+
aliases.each do |type_uri, name|
|
341
|
+
count_s = ax_args["count." + name]
|
342
|
+
count = count_s.to_i
|
343
|
+
if count_s.nil?
|
344
|
+
value = ax_args["value." + name]
|
345
|
+
if value.nil?
|
346
|
+
raise IndexError, "Missing #{"value." + name} in FetchResponse"
|
347
|
+
elsif value.empty?
|
348
|
+
values = []
|
349
|
+
else
|
350
|
+
values = [value]
|
351
|
+
end
|
352
|
+
elsif count_s.to_i == 0
|
353
|
+
values = []
|
354
|
+
else
|
355
|
+
values = (1..count).inject([]) do |l, i|
|
356
|
+
key = "value.#{name}.#{i}"
|
357
|
+
v = ax_args[key]
|
358
|
+
raise IndexError, "Missing #{key} in FetchResponse" if v.nil?
|
359
|
+
|
360
|
+
l << v
|
361
|
+
end
|
362
|
+
end
|
363
|
+
@data[type_uri] = values
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
# Get a single value for an attribute. If no value was sent
|
368
|
+
# for this attribute, use the supplied default. If there is more
|
369
|
+
# than one value for this attribute, this method will fail.
|
370
|
+
def get_single(type_uri, default = nil)
|
371
|
+
values = @data[type_uri]
|
372
|
+
return default if values.empty?
|
373
|
+
raise Error, "More than one value present for #{type_uri.inspect}" if values.size != 1
|
374
|
+
|
375
|
+
values[0]
|
376
|
+
end
|
377
|
+
|
378
|
+
# retrieve the list of values for this attribute
|
379
|
+
def get(type_uri)
|
380
|
+
@data[type_uri]
|
381
|
+
end
|
382
|
+
|
383
|
+
# retrieve the list of values for this attribute
|
384
|
+
def [](type_uri)
|
385
|
+
@data[type_uri]
|
386
|
+
end
|
387
|
+
|
388
|
+
# get the number of responses for this attribute
|
389
|
+
def count(type_uri)
|
390
|
+
@data[type_uri].size
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
# A fetch_response attribute exchange message
|
395
|
+
class FetchResponse < KeyValueMessage
|
396
|
+
attr_reader :update_url
|
397
|
+
# Use the aliases variable to manually add alias names in the response.
|
398
|
+
# They'll be returned to the client in the format:
|
399
|
+
# openid.ax.type.email=http://openid.net/schema/contact/internet/email
|
400
|
+
# openid.ax.value.email=guy@example.com
|
401
|
+
attr_accessor :aliases
|
402
|
+
|
403
|
+
def initialize(update_url = nil)
|
404
|
+
super()
|
405
|
+
@mode = "fetch_response"
|
406
|
+
@update_url = update_url
|
407
|
+
@aliases = NamespaceMap.new
|
408
|
+
end
|
409
|
+
|
410
|
+
# Serialize this object into arguments in the attribute
|
411
|
+
# exchange namespace
|
412
|
+
# Takes an optional FetchRequest. If specified, the response will be
|
413
|
+
# validated against this request, and empty responses for requested
|
414
|
+
# fields with no data will be sent.
|
415
|
+
def get_extension_args(request = nil)
|
416
|
+
zero_value_types = []
|
417
|
+
|
418
|
+
if request
|
419
|
+
# Validate the data in the context of the request (the
|
420
|
+
# same attributes should be present in each, and the
|
421
|
+
# counts in the response must be no more than the counts
|
422
|
+
# in the request)
|
423
|
+
@data.keys.each do |type_uri|
|
424
|
+
unless request.member?(type_uri)
|
425
|
+
raise IndexError, "Response attribute not present in request: #{type_uri.inspect}"
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
request.attributes.each do |attr_info|
|
430
|
+
# Copy the aliases from the request so that reading
|
431
|
+
# the response in light of the request is easier
|
432
|
+
if attr_info.ns_alias.nil?
|
433
|
+
@aliases.add(attr_info.type_uri)
|
434
|
+
else
|
435
|
+
@aliases.add_alias(attr_info.type_uri, attr_info.ns_alias)
|
436
|
+
end
|
437
|
+
values = @data[attr_info.type_uri]
|
438
|
+
zero_value_types << attr_info if values.empty? # @data defaults to []
|
439
|
+
|
440
|
+
if attr_info.count != UNLIMITED_VALUES and attr_info.count < values.size
|
441
|
+
raise Error, "More than the number of requested values were specified for #{attr_info.type_uri.inspect}"
|
442
|
+
end
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
kv_args = _get_extension_kv_args(@aliases)
|
447
|
+
|
448
|
+
# Add the KV args into the response with the args that are
|
449
|
+
# unique to the fetch_response
|
450
|
+
ax_args = new_args
|
451
|
+
|
452
|
+
zero_value_types.each do |attr_info|
|
453
|
+
name = @aliases.get_alias(attr_info.type_uri)
|
454
|
+
kv_args["type." + name] = attr_info.type_uri
|
455
|
+
kv_args["count." + name] = "0"
|
456
|
+
end
|
457
|
+
update_url = (request and request.update_url or @update_url)
|
458
|
+
ax_args["update_url"] = update_url unless update_url.nil?
|
459
|
+
ax_args.update(kv_args)
|
460
|
+
ax_args
|
461
|
+
end
|
462
|
+
|
463
|
+
def parse_extension_args(ax_args)
|
464
|
+
super
|
465
|
+
@update_url = ax_args["update_url"]
|
466
|
+
end
|
467
|
+
|
468
|
+
# Construct a FetchResponse object from an OpenID library
|
469
|
+
# SuccessResponse object.
|
470
|
+
def self.from_success_response(success_response, signed = true)
|
471
|
+
obj = new
|
472
|
+
ax_args = if signed
|
473
|
+
success_response.get_signed_ns(obj.ns_uri)
|
474
|
+
else
|
475
|
+
success_response.message.get_args(obj.ns_uri)
|
476
|
+
end
|
477
|
+
|
478
|
+
begin
|
479
|
+
obj.parse_extension_args(ax_args)
|
480
|
+
obj
|
481
|
+
rescue Error
|
482
|
+
nil
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|
486
|
+
|
487
|
+
# A store request attribute exchange message representation
|
488
|
+
class StoreRequest < KeyValueMessage
|
489
|
+
MODE = "store_request"
|
490
|
+
|
491
|
+
def initialize
|
492
|
+
super
|
493
|
+
@mode = MODE
|
494
|
+
end
|
495
|
+
|
496
|
+
# Extract a StoreRequest from an OpenID message
|
497
|
+
# message: OpenID::Message
|
498
|
+
# return a StoreRequest or nil if AX arguments are not present
|
499
|
+
def self.from_openid_request(oidreq)
|
500
|
+
message = oidreq.message
|
501
|
+
ax_args = message.get_args(NS_URI)
|
502
|
+
return if ax_args.empty? or ax_args["mode"] != MODE
|
503
|
+
|
504
|
+
req = new
|
505
|
+
req.parse_extension_args(ax_args)
|
506
|
+
req
|
507
|
+
end
|
508
|
+
|
509
|
+
def get_extension_args(aliases = nil)
|
510
|
+
ax_args = new_args
|
511
|
+
kv_args = _get_extension_kv_args(aliases)
|
512
|
+
ax_args.update(kv_args)
|
513
|
+
ax_args
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
# An indication that the store request was processed along with
|
518
|
+
# this OpenID transaction.
|
519
|
+
class StoreResponse < AXMessage
|
520
|
+
SUCCESS_MODE = "store_response_success"
|
521
|
+
FAILURE_MODE = "store_response_failure"
|
522
|
+
attr_reader :error_message
|
523
|
+
|
524
|
+
def initialize(succeeded = true, error_message = nil)
|
525
|
+
super()
|
526
|
+
raise Error, "Error message included in a success response" if succeeded and error_message
|
527
|
+
|
528
|
+
@mode = if succeeded
|
529
|
+
SUCCESS_MODE
|
530
|
+
else
|
531
|
+
FAILURE_MODE
|
532
|
+
end
|
533
|
+
@error_message = error_message
|
534
|
+
end
|
535
|
+
|
536
|
+
def self.from_success_response(success_response)
|
537
|
+
ax_args = success_response.message.get_args(NS_URI)
|
538
|
+
ax_args.key?("error") ? new(false, ax_args["error"]) : new
|
539
|
+
end
|
540
|
+
|
541
|
+
def succeeded?
|
542
|
+
@mode == SUCCESS_MODE
|
543
|
+
end
|
544
|
+
|
545
|
+
def get_extension_args
|
546
|
+
ax_args = new_args
|
547
|
+
ax_args["error"] = @error_message if !succeeded? and error_message
|
548
|
+
ax_args
|
549
|
+
end
|
550
|
+
end
|
551
|
+
end
|
552
|
+
end
|