ruby-openid2 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|