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
@@ -0,0 +1,354 @@
|
|
1
|
+
require_relative "../dh"
|
2
|
+
require_relative "../util"
|
3
|
+
require_relative "../kvpost"
|
4
|
+
require_relative "../cryptutil"
|
5
|
+
require_relative "../protocolerror"
|
6
|
+
require_relative "../association"
|
7
|
+
|
8
|
+
module OpenID
|
9
|
+
class Consumer
|
10
|
+
# A superclass for implementing Diffie-Hellman association sessions.
|
11
|
+
class DiffieHellmanSession
|
12
|
+
class << self
|
13
|
+
attr_reader :session_type,
|
14
|
+
:secret_size,
|
15
|
+
:allowed_assoc_types,
|
16
|
+
:hashfunc
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(dh = nil)
|
20
|
+
dh = DiffieHellman.from_defaults if dh.nil?
|
21
|
+
@dh = dh
|
22
|
+
end
|
23
|
+
|
24
|
+
# Return the query parameters for requesting an association
|
25
|
+
# using this Diffie-Hellman association session
|
26
|
+
def get_request
|
27
|
+
args = {"dh_consumer_public" => CryptUtil.num_to_base64(@dh.public)}
|
28
|
+
unless @dh.using_default_values?
|
29
|
+
args["dh_modulus"] = CryptUtil.num_to_base64(@dh.modulus)
|
30
|
+
args["dh_gen"] = CryptUtil.num_to_base64(@dh.generator)
|
31
|
+
end
|
32
|
+
|
33
|
+
args
|
34
|
+
end
|
35
|
+
|
36
|
+
# Process the response from a successful association request and
|
37
|
+
# return the shared secret for this association
|
38
|
+
def extract_secret(response)
|
39
|
+
dh_server_public64 = response.get_arg(
|
40
|
+
OPENID_NS,
|
41
|
+
"dh_server_public",
|
42
|
+
NO_DEFAULT,
|
43
|
+
)
|
44
|
+
enc_mac_key64 = response.get_arg(OPENID_NS, "enc_mac_key", NO_DEFAULT)
|
45
|
+
dh_server_public = CryptUtil.base64_to_num(dh_server_public64)
|
46
|
+
enc_mac_key = Util.from_base64(enc_mac_key64)
|
47
|
+
@dh.xor_secret(
|
48
|
+
self.class.hashfunc,
|
49
|
+
dh_server_public,
|
50
|
+
enc_mac_key,
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# A Diffie-Hellman association session that uses SHA1 as its hash
|
56
|
+
# function
|
57
|
+
class DiffieHellmanSHA1Session < DiffieHellmanSession
|
58
|
+
@session_type = "DH-SHA1"
|
59
|
+
@secret_size = 20
|
60
|
+
@allowed_assoc_types = ["HMAC-SHA1"]
|
61
|
+
@hashfunc = CryptUtil.method(:sha1)
|
62
|
+
end
|
63
|
+
|
64
|
+
# A Diffie-Hellman association session that uses SHA256 as its hash
|
65
|
+
# function
|
66
|
+
class DiffieHellmanSHA256Session < DiffieHellmanSession
|
67
|
+
@session_type = "DH-SHA256"
|
68
|
+
@secret_size = 32
|
69
|
+
@allowed_assoc_types = ["HMAC-SHA256"]
|
70
|
+
@hashfunc = CryptUtil.method(:sha256)
|
71
|
+
end
|
72
|
+
|
73
|
+
# An association session that does not use encryption
|
74
|
+
class NoEncryptionSession
|
75
|
+
class << self
|
76
|
+
attr_reader :session_type, :allowed_assoc_types
|
77
|
+
end
|
78
|
+
@session_type = "no-encryption"
|
79
|
+
@allowed_assoc_types = %w[HMAC-SHA1 HMAC-SHA256]
|
80
|
+
|
81
|
+
def get_request
|
82
|
+
{}
|
83
|
+
end
|
84
|
+
|
85
|
+
def extract_secret(response)
|
86
|
+
mac_key64 = response.get_arg(OPENID_NS, "mac_key", NO_DEFAULT)
|
87
|
+
Util.from_base64(mac_key64)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# An object that manages creating and storing associations for an
|
92
|
+
# OpenID provider endpoint
|
93
|
+
class AssociationManager
|
94
|
+
def self.create_session(session_type)
|
95
|
+
case session_type
|
96
|
+
when "no-encryption"
|
97
|
+
NoEncryptionSession.new
|
98
|
+
when "DH-SHA1"
|
99
|
+
DiffieHellmanSHA1Session.new
|
100
|
+
when "DH-SHA256"
|
101
|
+
DiffieHellmanSHA256Session.new
|
102
|
+
else
|
103
|
+
raise ArgumentError, "Unknown association session type: " \
|
104
|
+
"#{session_type.inspect}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def initialize(store, server_url, compatibility_mode = false,
|
109
|
+
negotiator = nil)
|
110
|
+
@store = store
|
111
|
+
@server_url = server_url
|
112
|
+
@compatibility_mode = compatibility_mode
|
113
|
+
@negotiator = negotiator || DefaultNegotiator
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_association
|
117
|
+
return if @store.nil?
|
118
|
+
|
119
|
+
assoc = @store.get_association(@server_url)
|
120
|
+
if assoc.nil? || assoc.expires_in <= 0
|
121
|
+
assoc = negotiate_association
|
122
|
+
@store.store_association(@server_url, assoc) unless assoc.nil?
|
123
|
+
end
|
124
|
+
|
125
|
+
assoc
|
126
|
+
end
|
127
|
+
|
128
|
+
def negotiate_association
|
129
|
+
assoc_type, session_type = @negotiator.get_allowed_type
|
130
|
+
begin
|
131
|
+
request_association(assoc_type, session_type)
|
132
|
+
rescue ServerError => e
|
133
|
+
supported_types = extract_supported_association_type(e, assoc_type)
|
134
|
+
unless supported_types.nil?
|
135
|
+
# Attempt to create an association from the assoc_type and
|
136
|
+
# session_type that the server told us it supported.
|
137
|
+
assoc_type, session_type = supported_types
|
138
|
+
begin
|
139
|
+
request_association(assoc_type, session_type)
|
140
|
+
rescue ServerError
|
141
|
+
Util.log("Server #{@server_url} refused its suggested " \
|
142
|
+
"association type: session_type=#{session_type}, " \
|
143
|
+
"assoc_type=#{assoc_type}")
|
144
|
+
nil
|
145
|
+
end
|
146
|
+
end
|
147
|
+
rescue InvalidOpenIDNamespace
|
148
|
+
Util.log("Server #{@server_url} returned a malformed association " \
|
149
|
+
"response. Falling back to check_id mode for this request.")
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
protected
|
155
|
+
|
156
|
+
def extract_supported_association_type(server_error, assoc_type)
|
157
|
+
# Any error message whose code is not 'unsupported-type' should
|
158
|
+
# be considered a total failure.
|
159
|
+
if server_error.error_code != "unsupported-type" or
|
160
|
+
server_error.message.is_openid1
|
161
|
+
Util.log("Server error when requesting an association from " \
|
162
|
+
"#{@server_url}: #{server_error.error_text}")
|
163
|
+
return
|
164
|
+
end
|
165
|
+
|
166
|
+
# The server didn't like the association/session type that we
|
167
|
+
# sent, and it sent us back a message that might tell us how to
|
168
|
+
# handle it.
|
169
|
+
Util.log("Unsupported association type #{assoc_type}: " \
|
170
|
+
"#{server_error.error_text}")
|
171
|
+
|
172
|
+
# Extract the session_type and assoc_type from the error message
|
173
|
+
assoc_type = server_error.message.get_arg(OPENID_NS, "assoc_type")
|
174
|
+
session_type = server_error.message.get_arg(OPENID_NS, "session_type")
|
175
|
+
|
176
|
+
if assoc_type.nil? or session_type.nil?
|
177
|
+
Util.log("Server #{@server_url} responded with unsupported " \
|
178
|
+
"association session but did not supply a fallback.")
|
179
|
+
nil
|
180
|
+
elsif !@negotiator.allowed?(assoc_type, session_type)
|
181
|
+
Util.log("Server sent unsupported session/association type: " \
|
182
|
+
"session_type=#{session_type}, assoc_type=#{assoc_type}")
|
183
|
+
nil
|
184
|
+
else
|
185
|
+
[assoc_type, session_type]
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# Make and process one association request to this endpoint's OP
|
190
|
+
# endpoint URL. Returns an association object or nil if the
|
191
|
+
# association processing failed. Raises ServerError when the
|
192
|
+
# remote OpenID server returns an error.
|
193
|
+
def request_association(assoc_type, session_type)
|
194
|
+
assoc_session, args = create_associate_request(assoc_type, session_type)
|
195
|
+
|
196
|
+
begin
|
197
|
+
response = OpenID.make_kv_post(args, @server_url)
|
198
|
+
extract_association(response, assoc_session)
|
199
|
+
rescue HTTPStatusError => e
|
200
|
+
Util.log("Got HTTP status error when requesting association: #{e}")
|
201
|
+
nil
|
202
|
+
rescue Message::KeyNotFound => e
|
203
|
+
Util.log("Missing required parameter in response from " \
|
204
|
+
"#{@server_url}: #{e}")
|
205
|
+
nil
|
206
|
+
rescue ProtocolError => e
|
207
|
+
Util.log("Protocol error processing response from #{@server_url}: " \
|
208
|
+
"#{e}")
|
209
|
+
nil
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# Create an association request for the given assoc_type and
|
214
|
+
# session_type. Returns a pair of the association session object
|
215
|
+
# and the request message that will be sent to the server.
|
216
|
+
def create_associate_request(assoc_type, session_type)
|
217
|
+
assoc_session = self.class.create_session(session_type)
|
218
|
+
args = {
|
219
|
+
"mode" => "associate",
|
220
|
+
"assoc_type" => assoc_type,
|
221
|
+
}
|
222
|
+
|
223
|
+
args["ns"] = OPENID2_NS unless @compatibility_mode
|
224
|
+
|
225
|
+
# Leave out the session type if we're in compatibility mode
|
226
|
+
# *and* it's no-encryption.
|
227
|
+
if !@compatibility_mode ||
|
228
|
+
assoc_session.class.session_type != "no-encryption"
|
229
|
+
args["session_type"] = assoc_session.class.session_type
|
230
|
+
end
|
231
|
+
|
232
|
+
args.merge!(assoc_session.get_request)
|
233
|
+
message = Message.from_openid_args(args)
|
234
|
+
[assoc_session, message]
|
235
|
+
end
|
236
|
+
|
237
|
+
# Given an association response message, extract the OpenID 1.X
|
238
|
+
# session type. Returns the association type for this message
|
239
|
+
#
|
240
|
+
# This function mostly takes care of the 'no-encryption' default
|
241
|
+
# behavior in OpenID 1.
|
242
|
+
#
|
243
|
+
# If the association type is plain-text, this function will
|
244
|
+
# return 'no-encryption'
|
245
|
+
def get_openid1_session_type(assoc_response)
|
246
|
+
# If it's an OpenID 1 message, allow session_type to default
|
247
|
+
# to nil (which signifies "no-encryption")
|
248
|
+
session_type = assoc_response.get_arg(OPENID_NS, "session_type")
|
249
|
+
|
250
|
+
# Handle the differences between no-encryption association
|
251
|
+
# respones in OpenID 1 and 2:
|
252
|
+
|
253
|
+
# no-encryption is not really a valid session type for
|
254
|
+
# OpenID 1, but we'll accept it anyway, while issuing a
|
255
|
+
# warning.
|
256
|
+
if session_type == "no-encryption"
|
257
|
+
Util.log("WARNING: #{@server_url} sent 'no-encryption'" \
|
258
|
+
"for OpenID 1.X")
|
259
|
+
|
260
|
+
# Missing or empty session type is the way to flag a
|
261
|
+
# 'no-encryption' response. Change the session type to
|
262
|
+
# 'no-encryption' so that it can be handled in the same
|
263
|
+
# way as OpenID 2 'no-encryption' respones.
|
264
|
+
elsif session_type == "" || session_type.nil?
|
265
|
+
session_type = "no-encryption"
|
266
|
+
end
|
267
|
+
|
268
|
+
session_type
|
269
|
+
end
|
270
|
+
|
271
|
+
def self.extract_expires_in(message)
|
272
|
+
# expires_in should be a base-10 string.
|
273
|
+
expires_in_str = message.get_arg(OPENID_NS, "expires_in", NO_DEFAULT)
|
274
|
+
raise ProtocolError, "Invalid expires_in field: #{expires_in_str}" unless /\A\d+\Z/.match?(expires_in_str)
|
275
|
+
|
276
|
+
expires_in_str.to_i
|
277
|
+
end
|
278
|
+
|
279
|
+
# Attempt to extract an association from the response, given the
|
280
|
+
# association response message and the established association
|
281
|
+
# session.
|
282
|
+
def extract_association(assoc_response, assoc_session)
|
283
|
+
# Extract the common fields from the response, raising an
|
284
|
+
# exception if they are not found
|
285
|
+
assoc_type = assoc_response.get_arg(
|
286
|
+
OPENID_NS,
|
287
|
+
"assoc_type",
|
288
|
+
NO_DEFAULT,
|
289
|
+
)
|
290
|
+
assoc_handle = assoc_response.get_arg(
|
291
|
+
OPENID_NS,
|
292
|
+
"assoc_handle",
|
293
|
+
NO_DEFAULT,
|
294
|
+
)
|
295
|
+
expires_in = self.class.extract_expires_in(assoc_response)
|
296
|
+
|
297
|
+
# OpenID 1 has funny association session behaviour.
|
298
|
+
session_type = if assoc_response.is_openid1
|
299
|
+
get_openid1_session_type(assoc_response)
|
300
|
+
else
|
301
|
+
assoc_response.get_arg(
|
302
|
+
OPENID2_NS,
|
303
|
+
"session_type",
|
304
|
+
NO_DEFAULT,
|
305
|
+
)
|
306
|
+
end
|
307
|
+
|
308
|
+
# Session type mismatch
|
309
|
+
if assoc_session.class.session_type != session_type
|
310
|
+
if assoc_response.is_openid1 and session_type == "no-encryption"
|
311
|
+
# In OpenID 1, any association request can result in a
|
312
|
+
# 'no-encryption' association response. Setting
|
313
|
+
# assoc_session to a new no-encryption session should
|
314
|
+
# make the rest of this function work properly for
|
315
|
+
# that case.
|
316
|
+
assoc_session = NoEncryptionSession.new
|
317
|
+
else
|
318
|
+
# Any other mismatch, regardless of protocol version
|
319
|
+
# results in the failure of the association session
|
320
|
+
# altogether.
|
321
|
+
raise ProtocolError, "Session type mismatch. Expected " \
|
322
|
+
"#{assoc_session.class.session_type}, got " \
|
323
|
+
"#{session_type}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# Make sure assoc_type is valid for session_type
|
328
|
+
unless assoc_session.class.allowed_assoc_types.member?(assoc_type)
|
329
|
+
raise ProtocolError, "Unsupported assoc_type for session " \
|
330
|
+
"#{assoc_session.class.session_type} " \
|
331
|
+
"returned: #{assoc_type}"
|
332
|
+
end
|
333
|
+
|
334
|
+
# Delegate to the association session to extract the secret
|
335
|
+
# from the response, however is appropriate for that session
|
336
|
+
# type.
|
337
|
+
begin
|
338
|
+
secret = assoc_session.extract_secret(assoc_response)
|
339
|
+
rescue Message::KeyNotFound, ArgumentError => e
|
340
|
+
raise ProtocolError, "Malformed response for " \
|
341
|
+
"#{assoc_session.class.session_type} " \
|
342
|
+
"session: #{e.message}"
|
343
|
+
end
|
344
|
+
|
345
|
+
Association.from_expires_in(
|
346
|
+
expires_in,
|
347
|
+
assoc_handle,
|
348
|
+
secret,
|
349
|
+
assoc_type,
|
350
|
+
)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require_relative "../message"
|
2
|
+
require_relative "../util"
|
3
|
+
|
4
|
+
module OpenID
|
5
|
+
class Consumer
|
6
|
+
# An object that holds the state necessary for generating an
|
7
|
+
# OpenID authentication request. This object holds the association
|
8
|
+
# with the server and the discovered information with which the
|
9
|
+
# request will be made.
|
10
|
+
#
|
11
|
+
# It is separate from the consumer because you may wish to add
|
12
|
+
# things to the request before sending it on its way to the
|
13
|
+
# server. It also has serialization options that let you encode
|
14
|
+
# the authentication request as a URL or as a form POST.
|
15
|
+
class CheckIDRequest
|
16
|
+
attr_accessor :return_to_args, :message
|
17
|
+
attr_reader :endpoint, :anonymous
|
18
|
+
|
19
|
+
# Users of this library should not create instances of this
|
20
|
+
# class. Instances of this class are created by the library
|
21
|
+
# when needed.
|
22
|
+
def initialize(assoc, endpoint)
|
23
|
+
@assoc = assoc
|
24
|
+
@endpoint = endpoint
|
25
|
+
@return_to_args = {}
|
26
|
+
@message = Message.new(endpoint.preferred_namespace)
|
27
|
+
@anonymous = false
|
28
|
+
end
|
29
|
+
|
30
|
+
# Set whether this request should be made anonymously. If a
|
31
|
+
# request is anonymous, the identifier will not be sent in the
|
32
|
+
# request. This is only useful if you are making another kind of
|
33
|
+
# request with an extension in this request.
|
34
|
+
#
|
35
|
+
# Anonymous requests are not allowed when the request is made
|
36
|
+
# with OpenID 1.
|
37
|
+
def anonymous=(is_anonymous)
|
38
|
+
if is_anonymous && @message.is_openid1
|
39
|
+
raise ArgumentError, "OpenID1 requests MUST include the " \
|
40
|
+
"identifier in the request"
|
41
|
+
end
|
42
|
+
@anonymous = is_anonymous
|
43
|
+
end
|
44
|
+
|
45
|
+
# Add an object that implements the extension interface for
|
46
|
+
# adding arguments to an OpenID message to this checkid request.
|
47
|
+
#
|
48
|
+
# extension_request: an OpenID::Extension object.
|
49
|
+
def add_extension(extension_request)
|
50
|
+
extension_request.to_message(@message)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Add an extension argument to this OpenID authentication
|
54
|
+
# request. You probably want to use add_extension and the
|
55
|
+
# OpenID::Extension interface.
|
56
|
+
#
|
57
|
+
# Use caution when adding arguments, because they will be
|
58
|
+
# URL-escaped and appended to the redirect URL, which can easily
|
59
|
+
# get quite long.
|
60
|
+
def add_extension_arg(namespace, key, value)
|
61
|
+
@message.set_arg(namespace, key, value)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Produce a OpenID::Message representing this request.
|
65
|
+
#
|
66
|
+
# Not specifying a return_to URL means that the user will not be
|
67
|
+
# returned to the site issuing the request upon its completion.
|
68
|
+
#
|
69
|
+
# If immediate mode is requested, the OpenID provider is to send
|
70
|
+
# back a response immediately, useful for behind-the-scenes
|
71
|
+
# authentication attempts. Otherwise the OpenID provider may
|
72
|
+
# engage the user before providing a response. This is the
|
73
|
+
# default case, as the user may need to provide credentials or
|
74
|
+
# approve the request before a positive response can be sent.
|
75
|
+
def get_message(realm, return_to = nil, immediate = false)
|
76
|
+
if !return_to.nil?
|
77
|
+
return_to = Util.append_args(return_to, @return_to_args)
|
78
|
+
elsif immediate
|
79
|
+
raise ArgumentError, '"return_to" is mandatory when using ' \
|
80
|
+
'"checkid_immediate"'
|
81
|
+
elsif @message.is_openid1
|
82
|
+
raise ArgumentError, '"return_to" is mandatory for OpenID 1 ' \
|
83
|
+
"requests"
|
84
|
+
elsif @return_to_args.empty?
|
85
|
+
raise ArgumentError, 'extra "return_to" arguments were specified, ' \
|
86
|
+
"but no return_to was specified"
|
87
|
+
end
|
88
|
+
|
89
|
+
message = @message.copy
|
90
|
+
|
91
|
+
mode = immediate ? "checkid_immediate" : "checkid_setup"
|
92
|
+
message.set_arg(OPENID_NS, "mode", mode)
|
93
|
+
|
94
|
+
realm_key = message.is_openid1 ? "trust_root" : "realm"
|
95
|
+
message.set_arg(OPENID_NS, realm_key, realm)
|
96
|
+
|
97
|
+
message.set_arg(OPENID_NS, "return_to", return_to) unless return_to.nil?
|
98
|
+
|
99
|
+
unless @anonymous
|
100
|
+
if @endpoint.is_op_identifier
|
101
|
+
# This will never happen when we're in OpenID 1
|
102
|
+
# compatibility mode, as long as is_op_identifier()
|
103
|
+
# returns false whenever preferred_namespace returns
|
104
|
+
# OPENID1_NS.
|
105
|
+
claimed_id = request_identity = IDENTIFIER_SELECT
|
106
|
+
else
|
107
|
+
request_identity = @endpoint.get_local_id
|
108
|
+
claimed_id = @endpoint.claimed_id
|
109
|
+
end
|
110
|
+
|
111
|
+
# This is true for both OpenID 1 and 2
|
112
|
+
message.set_arg(OPENID_NS, "identity", request_identity)
|
113
|
+
|
114
|
+
message.set_arg(OPENID2_NS, "claimed_id", claimed_id) if message.is_openid2
|
115
|
+
end
|
116
|
+
|
117
|
+
if @assoc && (message.is_openid1 || !%w[checkid_setup checkid_immediate].include?(mode))
|
118
|
+
message.set_arg(OPENID_NS, "assoc_handle", @assoc.handle)
|
119
|
+
assoc_log_msg = "with assocication #{@assoc.handle}"
|
120
|
+
else
|
121
|
+
assoc_log_msg = "using stateless mode."
|
122
|
+
end
|
123
|
+
|
124
|
+
Util.log("Generated #{mode} request to #{@endpoint.server_url} " \
|
125
|
+
"#{assoc_log_msg}")
|
126
|
+
message
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns a URL with an encoded OpenID request.
|
130
|
+
#
|
131
|
+
# The resulting URL is the OpenID provider's endpoint URL with
|
132
|
+
# parameters appended as query arguments. You should redirect
|
133
|
+
# the user agent to this URL.
|
134
|
+
#
|
135
|
+
# OpenID 2.0 endpoints also accept POST requests, see
|
136
|
+
# 'send_redirect?' and 'form_markup'.
|
137
|
+
def redirect_url(realm, return_to = nil, immediate = false)
|
138
|
+
message = get_message(realm, return_to, immediate)
|
139
|
+
message.to_url(@endpoint.server_url)
|
140
|
+
end
|
141
|
+
|
142
|
+
# Get html for a form to submit this request to the IDP.
|
143
|
+
#
|
144
|
+
# form_tag_attrs is a hash of attributes to be added to the form
|
145
|
+
# tag. 'accept-charset' and 'enctype' have defaults that can be
|
146
|
+
# overridden. If a value is supplied for 'action' or 'method',
|
147
|
+
# it will be replaced.
|
148
|
+
def form_markup(realm, return_to = nil, immediate = false,
|
149
|
+
form_tag_attrs = nil)
|
150
|
+
message = get_message(realm, return_to, immediate)
|
151
|
+
message.to_form_markup(@endpoint.server_url, form_tag_attrs)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Get a complete HTML document that autosubmits the request to the IDP
|
155
|
+
# with javascript. This method wraps form_markup - see that method's
|
156
|
+
# documentation for help with the parameters.
|
157
|
+
def html_markup(realm, return_to = nil, immediate = false,
|
158
|
+
form_tag_attrs = nil)
|
159
|
+
Util.auto_submit_html(form_markup(
|
160
|
+
realm,
|
161
|
+
return_to,
|
162
|
+
immediate,
|
163
|
+
form_tag_attrs,
|
164
|
+
))
|
165
|
+
end
|
166
|
+
|
167
|
+
# Should this OpenID authentication request be sent as a HTTP
|
168
|
+
# redirect or as a POST (form submission)?
|
169
|
+
#
|
170
|
+
# This takes the same parameters as redirect_url or form_markup
|
171
|
+
def send_redirect?(realm, return_to = nil, immediate = false)
|
172
|
+
return true if @endpoint.compatibility_mode
|
173
|
+
|
174
|
+
url = redirect_url(realm, return_to, immediate)
|
175
|
+
url.length <= OPENID1_URL_LIMIT
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|