rack 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

Files changed (50) hide show
  1. data/AUTHORS +1 -0
  2. data/RDOX +61 -3
  3. data/README +94 -9
  4. data/Rakefile +36 -32
  5. data/SPEC +1 -7
  6. data/bin/rackup +31 -13
  7. data/lib/rack.rb +8 -19
  8. data/lib/rack/auth/digest/params.rb +2 -2
  9. data/lib/rack/auth/openid.rb +406 -80
  10. data/lib/rack/builder.rb +1 -1
  11. data/lib/rack/cascade.rb +10 -0
  12. data/lib/rack/commonlogger.rb +6 -1
  13. data/lib/rack/deflater.rb +63 -0
  14. data/lib/rack/directory.rb +158 -0
  15. data/lib/rack/file.rb +11 -5
  16. data/lib/rack/handler.rb +44 -0
  17. data/lib/rack/handler/evented_mongrel.rb +8 -0
  18. data/lib/rack/handler/fastcgi.rb +1 -0
  19. data/lib/rack/handler/mongrel.rb +21 -1
  20. data/lib/rack/lint.rb +20 -13
  21. data/lib/rack/mock.rb +1 -0
  22. data/lib/rack/request.rb +69 -2
  23. data/lib/rack/session/abstract/id.rb +140 -0
  24. data/lib/rack/session/memcache.rb +97 -0
  25. data/lib/rack/session/pool.rb +50 -59
  26. data/lib/rack/showstatus.rb +3 -1
  27. data/lib/rack/urlmap.rb +12 -12
  28. data/lib/rack/utils.rb +88 -9
  29. data/test/cgi/lighttpd.conf +1 -1
  30. data/test/cgi/test.fcgi +1 -2
  31. data/test/cgi/test.ru +2 -2
  32. data/test/spec_rack_auth_openid.rb +137 -0
  33. data/test/spec_rack_camping.rb +37 -33
  34. data/test/spec_rack_cascade.rb +15 -0
  35. data/test/spec_rack_cgi.rb +4 -3
  36. data/test/spec_rack_deflater.rb +70 -0
  37. data/test/spec_rack_directory.rb +56 -0
  38. data/test/spec_rack_fastcgi.rb +4 -3
  39. data/test/spec_rack_file.rb +11 -1
  40. data/test/spec_rack_handler.rb +24 -0
  41. data/test/spec_rack_lint.rb +19 -33
  42. data/test/spec_rack_mongrel.rb +71 -0
  43. data/test/spec_rack_request.rb +91 -1
  44. data/test/spec_rack_session_memcache.rb +132 -0
  45. data/test/spec_rack_session_pool.rb +48 -1
  46. data/test/spec_rack_showstatus.rb +5 -4
  47. data/test/spec_rack_urlmap.rb +60 -25
  48. data/test/spec_rack_utils.rb +118 -1
  49. data/test/testrequest.rb +3 -1
  50. metadata +67 -44
data/bin/rackup CHANGED
@@ -6,6 +6,8 @@ require 'optparse'
6
6
  automatic = false
7
7
  server = nil
8
8
  env = "development"
9
+ daemonize = false
10
+ pid = nil
9
11
  options = {:Port => 9292, :Host => "0.0.0.0", :AccessLog => []}
10
12
 
11
13
  opts = OptionParser.new("", 24, ' ') { |opts|
@@ -55,6 +57,14 @@ opts = OptionParser.new("", 24, ' ') { |opts|
55
57
  env = e
56
58
  }
57
59
 
60
+ opts.on("-D", "--daemonize", "run daemonized in the background") { |d|
61
+ daemonize = d ? true : false
62
+ }
63
+
64
+ opts.on("-P", "--pid FILE", "file to store PID (default: rack.pid)") { |f|
65
+ pid = File.expand_path(f)
66
+ }
67
+
58
68
  opts.separator ""
59
69
  opts.separator "Common options:"
60
70
 
@@ -93,8 +103,7 @@ else
93
103
  inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
94
104
  end
95
105
 
96
- case server
97
- when nil
106
+ unless server = Rack::Handler.get(server)
98
107
  # Guess.
99
108
  if ENV.include?("PHP_FCGI_CHILDREN")
100
109
  server = Rack::Handler::FastCGI
@@ -111,16 +120,6 @@ when nil
111
120
  server = Rack::Handler::WEBrick
112
121
  end
113
122
  end
114
- when "mongrel"
115
- server = Rack::Handler::Mongrel
116
- when "webrick"
117
- server = Rack::Handler::WEBrick
118
- when "cgi"
119
- server = Rack::Handler::CGI
120
- when "fastcgi"
121
- server = Rack::Handler::FastCGI
122
- else
123
- server = Rack::Handler.const_get(server.capitalize)
124
123
  end
125
124
 
126
125
  p server if $DEBUG
@@ -130,7 +129,6 @@ when "development"
130
129
  app = Rack::Builder.new {
131
130
  use Rack::CommonLogger, STDERR unless server.name =~ /CGI/
132
131
  use Rack::ShowExceptions
133
- use Rack::Reloader
134
132
  use Rack::Lint
135
133
  run inner_app
136
134
  }.to_app
@@ -151,4 +149,24 @@ if $DEBUG
151
149
  pp inner_app
152
150
  end
153
151
 
152
+ if daemonize
153
+ if RUBY_VERSION < "1.9"
154
+ exit if fork
155
+ Process.setsid
156
+ exit if fork
157
+ Dir.chdir "/"
158
+ File.umask 0000
159
+ STDIN.reopen "/dev/null"
160
+ STDOUT.reopen "/dev/null", "a"
161
+ STDERR.reopen "/dev/null", "a"
162
+ else
163
+ Process.daemon
164
+ end
165
+
166
+ if pid
167
+ File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
168
+ at_exit { File.delete(pid) if File.exist?(pid) }
169
+ end
170
+ end
171
+
154
172
  server.run app, options
@@ -21,11 +21,19 @@ module Rack
21
21
  VERSION.join(".")
22
22
  end
23
23
 
24
+ # Return the Rack release as a dotted string.
25
+ def self.release
26
+ "0.4"
27
+ end
28
+
24
29
  autoload :Builder, "rack/builder"
25
30
  autoload :Cascade, "rack/cascade"
26
31
  autoload :CommonLogger, "rack/commonlogger"
27
32
  autoload :File, "rack/file"
33
+ autoload :Deflater, "rack/deflater"
34
+ autoload :Directory, "rack/directory"
28
35
  autoload :ForwardRequest, "rack/recursive"
36
+ autoload :Handler, "rack/handler"
29
37
  autoload :Lint, "rack/lint"
30
38
  autoload :Recursive, "rack/recursive"
31
39
  autoload :Reloader, "rack/reloader"
@@ -70,23 +78,4 @@ module Rack
70
78
  module Adapter
71
79
  autoload :Camping, "rack/adapter/camping"
72
80
  end
73
-
74
- # *Handlers* connect web servers with Rack.
75
- #
76
- # Rack includes Handlers for Mongrel, WEBrick, FastCGI, CGI, SCGI
77
- # and LiteSpeed.
78
- #
79
- # Handlers usually are activated by calling <tt>MyHandler.run(myapp)</tt>.
80
- # A second optional hash can be passed to include server-specific
81
- # configuration.
82
-
83
- module Handler
84
- autoload :CGI, "rack/handler/cgi"
85
- autoload :FastCGI, "rack/handler/fastcgi"
86
- autoload :Mongrel, "rack/handler/mongrel"
87
- autoload :WEBrick, "rack/handler/webrick"
88
- autoload :LSWS, "rack/handler/lsws"
89
- autoload :SCGI, "rack/handler/scgi"
90
- end
91
81
  end
92
-
@@ -17,8 +17,8 @@ module Rack
17
17
  ret
18
18
  end
19
19
 
20
- def self.split_header_value(str) # From WEBrick::HTTPUtils
21
- str.scan(/((?:"(?:\\.|[^"])+?"|[^",]+)+)(?:,\s*|\Z)/n).collect{ |v| v[0] }
20
+ def self.split_header_value(str)
21
+ str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
22
22
  end
23
23
 
24
24
  def initialize
@@ -1,110 +1,436 @@
1
1
  # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
2
 
3
- require 'rack/auth/abstract/handler'
4
- require 'openid'
3
+ gem 'ruby-openid', '~> 2' if defined? Gem
4
+ require 'rack/auth/abstract/handler' #rack
5
+ require 'uri' #std
6
+ require 'pp' #std
7
+ require 'openid' #gem
8
+ require 'openid/extension' #gem
9
+ require 'openid/store/memory' #gem
5
10
 
6
11
  module Rack
7
12
  module Auth
8
- # Rack::Auth::OpenID provides a simple method for permitting openid
9
- # based logins. It requires the ruby-openid lib from janrain to operate,
10
- # as well as some method of session management of a Hash type.
13
+ # Rack::Auth::OpenID provides a simple method for permitting
14
+ # openid based logins. It requires the ruby-openid library from
15
+ # janrain to operate, as well as a rack method of session management.
11
16
  #
12
- # After a transaction, the response status object is stored in the
13
- # environment at rack.auth.openid.status, which can be used in the
14
- # followup block or in a wrapping application to accomplish
15
- # additional data maniipulation.
17
+ # The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
16
18
  #
17
- # NOTE: Due to the amount of data that ruby-openid stores in the session,
18
- # Rack::Session::Cookie may fault.
19
+ # The OpenID specifications can be found at
20
+ # http://openid.net/specs/openid-authentication-1_1.html
21
+ # and
22
+ # http://openid.net/specs/openid-authentication-2_0.html. Documentation
23
+ # for published OpenID extensions and related topics can be found at
24
+ # http://openid.net/developers/specs/.
19
25
  #
20
- # A hash of data is stored in the session hash at the key of :openid.
21
- # The fully canonicalized identity url is stored within at 'identity'.
22
- # Extension data from 'openid.sreg.nickname' would be stored as
23
- # { 'nickname' => value }.
26
+ # It is recommended to read through the OpenID spec, as well as
27
+ # ruby-openid's documentation, to understand what exactly goes on. However
28
+ # a setup as simple as the presented examples is enough to provide
29
+ # functionality.
24
30
  #
25
- # NOTE: To my knowledge there is no collision at this point from storage
26
- # of this manner, if there is please let me know so I may adjust this app
27
- # to cope.
31
+ # This library strongly intends to utilize the OpenID 2.0 features of the
32
+ # ruby-openid library, while maintaining OpenID 1.0 compatiblity.
33
+ #
34
+ # All responses from this rack application will be 303 redirects unless an
35
+ # error occurs, with the exception of an authentication request requiring
36
+ # an HTML form submission.
37
+ #
38
+ # NOTE: Extensions are not currently supported by this implimentation of
39
+ # the OpenID rack application due to the complexity of the current
40
+ # ruby-openid extension handling.
41
+ #
42
+ # NOTE: Due to the amount of data that this library stores in the
43
+ # session, Rack::Session::Cookie may fault.
28
44
  class OpenID < AbstractHandler
45
+ class NoSession < RuntimeError; end
29
46
  # Required for ruby-openid
30
- OIDStore = ::OpenID::MemoryStore.new
47
+ OIDStore = ::OpenID::Store::Memory.new
48
+ HTML = '<html><head><title>%s</title></head><body>%s</body></html>'
31
49
 
32
50
  # A Hash of options is taken as it's single initializing
33
- # argument. String keys are taken to be openid protocol
34
- # extension namespaces.
35
- #
36
- # For example: 'sreg' => { 'required' => # 'nickname' }
37
- #
38
- # Other keys are taken as options for Rack::Auth::OpenID, normally Symbols.
39
- # Only :return is required. :trust is highly recommended to be set.
40
- #
41
- # * :return defines the url to return to after the client authenticates
42
- # with the openid service provider. Should point to where this app is
43
- # mounted. (ex: 'http://mysite.com/openid')
44
- # * :trust defines the url identifying the site they are actually logging
45
- # into. (ex: 'http://mysite.com/')
46
- # * :session_key defines the key to the session hash in the env.
47
- # (by default it uses 'rack.session')
48
- def initialize(options={})
49
- raise ArgumentError, 'No return url provided.' unless options[:return]
50
- warn 'No trust url provided.' unless options[:trust]
51
- options[:trust] ||= options[:return]
52
-
53
- @options = {
54
- :session_key => 'rack.session'
51
+ # argument. For example:
52
+ #
53
+ # simple_oid = OpenID.new('http://mysite.com/')
54
+ #
55
+ # return_oid = OpenID.new('http://mysite.com/', {
56
+ # :return_to => 'http://mysite.com/openid'
57
+ # })
58
+ #
59
+ # page_oid = OpenID.new('http://mysite.com/',
60
+ # :login_good => 'http://mysite.com/auth_good'
61
+ # )
62
+ #
63
+ # complex_oid = OpenID.new('http://mysite.com/',
64
+ # :return_to => 'http://mysite.com/openid',
65
+ # :login_good => 'http://mysite.com/user/preferences',
66
+ # :auth_fail => [500, {'Content-Type'=>'text/plain'},
67
+ # 'Unable to negotiate with foreign server.'],
68
+ # :immediate => true,
69
+ # :extensions => {
70
+ # ::OpenID::SReg => [['email'],['nickname']]
71
+ # }
72
+ # )
73
+ #
74
+ # = Arguments
75
+ #
76
+ # The first argument is the realm, identifying the site they are trusting
77
+ # with their identity. This is required.
78
+ #
79
+ # NOTE: In OpenID 1.x, the realm or trust_root is optional and the
80
+ # return_to url is required. As this library strives tward ruby-openid
81
+ # 2.0, and OpenID 2.0 compatibiliy, the realm is required and return_to
82
+ # is optional. However, this implimentation is still backwards compatible
83
+ # with OpenID 1.0 servers.
84
+ #
85
+ # The optional second argument is a hash of options.
86
+ #
87
+ # == Options
88
+ #
89
+ # <tt>:return_to</tt> defines the url to return to after the client
90
+ # authenticates with the openid service provider. This url should point
91
+ # to where Rack::Auth::OpenID is mounted. If <tt>:return_to</tt> is not
92
+ # provided, :return_to will be the current url including all query
93
+ # parameters.
94
+ #
95
+ # <tt>:session_key</tt> defines the key to the session hash in the env.
96
+ # It defaults to 'rack.session'.
97
+ #
98
+ # <tt>:openid_param</tt> defines at what key in the request parameters to
99
+ # find the identifier to resolve. As per the 2.0 spec, the default is
100
+ # 'openid_identifier'.
101
+ #
102
+ # <tt>:immediate</tt> as true will make immediate type of requests the
103
+ # default. See OpenID specification documentation.
104
+ #
105
+ # === URL options
106
+ #
107
+ # <tt>:login_good</tt> is the url to go to after the authentication
108
+ # process has completed.
109
+ #
110
+ # <tt>:login_fail</tt> is the url to go to after the authentication
111
+ # process has failed.
112
+ #
113
+ # <tt>:login_quit</tt> is the url to go to after the authentication
114
+ # process
115
+ # has been cancelled.
116
+ #
117
+ # === Response options
118
+ #
119
+ # <tt>:no_session</tt> should be a rack response to be returned if no or
120
+ # an incompatible session is found.
121
+ #
122
+ # <tt>:auth_fail</tt> should be a rack response to be returned if an
123
+ # OpenID::DiscoveryFailure occurs. This is typically due to being unable
124
+ # to access the identity url or identity server.
125
+ #
126
+ # <tt>:error</tt> should be a rack response to return if any other
127
+ # generic error would occur and <tt>options[:catch_errors]</tt> is true.
128
+ #
129
+ # === Extensions
130
+ #
131
+ # <tt>:extensions</tt> should be a hash of openid extension
132
+ # implementations. The key should be the extension main module, the value
133
+ # should be an array of arguments for extension::Request.new
134
+ #
135
+ # The hash is iterated over and passed to #add_extension for processing.
136
+ # Please see #add_extension for further documentation.
137
+ def initialize(realm, options={})
138
+ @realm = realm
139
+ realm = URI(realm)
140
+ if realm.path.empty?
141
+ raise ArgumentError, "Invalid realm path: '#{realm.path}'"
142
+ elsif not realm.absolute?
143
+ raise ArgumentError, "Realm '#{@realm}' not absolute"
144
+ end
145
+
146
+ [:return_to, :login_good, :login_fail, :login_quit].each do |key|
147
+ if options.key? key and luri = URI(options[key])
148
+ if !luri.absolute?
149
+ raise ArgumentError, ":#{key} is not an absolute uri: '#{luri}'"
150
+ end
151
+ end
152
+ end
153
+
154
+ if options[:return_to] and ruri = URI(options[:return_to])
155
+ if ruri.path.empty?
156
+ raise ArgumentError, "Invalid return_to path: '#{ruri.path}'"
157
+ elsif realm.path != ruri.path[0, realm.path.size]
158
+ raise ArgumentError, 'return_to not within realm.' \
159
+ end
160
+ end
161
+
162
+ # TODO: extension support
163
+ if extensions = options.delete(:extensions)
164
+ extensions.each do |ext, args|
165
+ add_extension ext, *args
166
+ end
167
+ end
168
+
169
+ @options = {
170
+ :session_key => 'rack.session',
171
+ :openid_param => 'openid_identifier',
172
+ #:return_to, :login_good, :login_fail, :login_quit
173
+ #:no_session, :auth_fail, :error
174
+ :store => OIDStore,
175
+ :immediate => false,
176
+ :anonymous => false,
177
+ :catch_errors => false
55
178
  }.merge(options)
179
+ @extensions = {}
56
180
  end
57
181
 
182
+ attr_reader :options, :extensions
183
+
184
+ # It sets up and uses session data at <tt>:openid</tt> within the
185
+ # session. It sets up the ::OpenID::Consumer using the store specified by
186
+ # <tt>options[:store]</tt>.
187
+ #
188
+ # If the parameter specified by <tt>options[:openid_param]</tt> is
189
+ # present, processing is passed to #check and the result is returned.
190
+ #
191
+ # If the parameter 'openid.mode' is set, implying a followup from the
192
+ # openid server, processing is passed to #finish and the result is
193
+ # returned.
194
+ #
195
+ # If neither of these conditions are met, a 400 error is returned.
196
+ #
197
+ # If an error is thrown and <tt>options[:catch_errors]</tt> is false, the
198
+ # exception will be reraised. Otherwise a 500 error is returned.
58
199
  def call(env)
200
+ env['rack.auth.openid'] = self
201
+ session = env[@options[:session_key]]
202
+ unless session and session.is_a? Hash
203
+ raise(NoSession, 'No compatible session')
204
+ end
205
+ # let us work in our own namespace...
206
+ session = (session[:openid] ||= {})
207
+ unless session and session.is_a? Hash
208
+ raise(NoSession, 'Incompatible session')
209
+ end
210
+
59
211
  request = Rack::Request.new env
60
- return no_session unless session = request.env[@options[:session_key]]
61
- resp = if request.GET['openid.mode']
62
- finish session, request.GET, env
63
- elsif request.GET['openid_url']
64
- check session, request.GET['openid_url'], env
65
- else
66
- bad_request
67
- end
68
- end
212
+ consumer = ::OpenID::Consumer.new session, @options[:store]
213
+
214
+ if request.params['openid.mode']
215
+ finish consumer, session, request
216
+ elsif request.params[@options[:openid_param]]
217
+ check consumer, session, request
218
+ else
219
+ env['rack.errors'].puts "No valid params provided."
220
+ bad_request
221
+ end
222
+ rescue NoSession
223
+ env['rack.errors'].puts($!.message, *$@)
69
224
 
70
- def check(session, oid_url, env)
71
- consumer = ::OpenID::Consumer.new session, OIDStore
72
- oid = consumer.begin oid_url
73
- return auth_fail unless oid.status == ::OpenID::SUCCESS
74
- @options.each do |ns,s|
75
- next unless ns.is_a? String
76
- s.each {|k,v| oid.add_extension_arg(ns, k, v) }
225
+ @options. ### Missing or incompatible session
226
+ fetch :no_session, [ 500,
227
+ {'Content-Type'=>'text/plain'},
228
+ $!.message ]
229
+ rescue
230
+ env['rack.errors'].puts($!.message, *$@)
231
+
232
+ if not @options[:catch_error]
233
+ raise($!)
77
234
  end
78
- r_url = @options.fetch :return do |k| request.url end
79
- t_url = @options.fetch :trust
80
- env['rack.auth.openid.status'] = oid
81
- return 303, {'Location'=>oid.redirect_url( t_url, r_url )}, []
235
+ @options.
236
+ fetch :error, [ 500,
237
+ {'Content-Type'=>'text/plain'},
238
+ 'OpenID has encountered an error.' ]
82
239
  end
83
240
 
84
- def finish(session, params, env)
85
- consumer = ::OpenID::Consumer.new session, OIDStore
86
- oid = consumer.complete params
87
- return bad_login unless oid.status == ::OpenID::SUCCESS
88
- session[:openid] = {'identity' => oid.identity_url}
89
- @options.each do |ns,s|
90
- next unless ns.is_a? String
91
- oid.extension_response(ns).each{|k,v| session[k]=v }
241
+ # As the first part of OpenID consumer action, #check retrieves the data
242
+ # required for completion.
243
+ #
244
+ # * <tt>session[:openid][:openid_param]</tt> is set to the submitted
245
+ # identifier to be authenticated.
246
+ # * <tt>session[:openid][:site_return]</tt> is set as the request's
247
+ # HTTP_REFERER, unless already set.
248
+ # * <tt>env['rack.auth.openid.request']</tt> is the openid checkid
249
+ # request instance.
250
+ def check(consumer, session, req)
251
+ session[:openid_param] = req.params[@options[:openid_param]]
252
+ oid = consumer.begin(session[:openid_param], @options[:anonymous])
253
+ pp oid if $DEBUG
254
+ req.env['rack.auth.openid.request'] = oid
255
+
256
+ session[:site_return] ||= req.env['HTTP_REFERER']
257
+
258
+ # SETUP_NEEDED check!
259
+ # see OpenID::Consumer::CheckIDRequest docs
260
+ query_args = [@realm, *@options.values_at(:return_to, :immediate)]
261
+ query_args[1] ||= req.url
262
+ query_args[2] = false if session.key? :setup_needed
263
+ pp query_args if $DEBUG
264
+
265
+ ## Extension support
266
+ extensions.each do |ext,args|
267
+ oid.add_extension ext::Request.new(*args)
268
+ end
269
+
270
+ if oid.send_redirect?(*query_args)
271
+ redirect = oid.redirect_url(*query_args)
272
+ if $DEBUG
273
+ pp redirect
274
+ pp Rack::Utils.parse_query(URI(redirect).query)
275
+ end
276
+ [ 303, {'Location'=>redirect}, [] ]
277
+ else
278
+ # check on 'action' option.
279
+ formbody = oid.form_markup(*query_args)
280
+ if $DEBUG
281
+ pp formbody
282
+ end
283
+ body = HTML % ['Confirm...', formbody]
284
+ [ 200, {'Content-Type'=>'text/html'}, body.to_a ]
92
285
  end
93
- env['rack.auth.openid.status'] = oid
94
- return 303, {'Location'=>@options[:trust]}, []
286
+ rescue ::OpenID::DiscoveryFailure => e
287
+ # thrown from inside OpenID::Consumer#begin by yadis stuff
288
+ req.env['rack.errors'].puts($!.message, *$@)
289
+
290
+ @options. ### Foreign server failed
291
+ fetch :auth_fail, [ 503,
292
+ {'Content-Type'=>'text/plain'},
293
+ 'Foreign server failure.' ]
95
294
  end
96
295
 
97
- def no_session
98
- @options.
99
- fetch :no_session, [500,{'Content-Type'=>'text/plain'},'No session available.']
296
+ # This is the final portion of authentication. Unless any errors outside
297
+ # of specification occur, a 303 redirect will be returned with Location
298
+ # determined by the OpenID response type. If none of the response type
299
+ # :login_* urls are set, the redirect will be set to
300
+ # <tt>session[:openid][:site_return]</tt>. If
301
+ # <tt>session[:openid][:site_return]</tt> is unset, the realm will be
302
+ # used.
303
+ #
304
+ # Any messages from OpenID's response are appended to the 303 response
305
+ # body.
306
+ #
307
+ # Data gathered from extensions are stored in session[:openid] with the
308
+ # extension's namespace uri as the key.
309
+ #
310
+ # * <tt>env['rack.auth.openid.response']</tt> is the openid response.
311
+ #
312
+ # The four valid possible outcomes are:
313
+ # * failure: <tt>options[:login_fail]</tt> or
314
+ # <tt>session[:site_return]</tt> or the realm
315
+ # * <tt>session[:openid]</tt> is cleared and any messages are send to
316
+ # rack.errors
317
+ # * <tt>session[:openid]['authenticated']</tt> is <tt>false</tt>
318
+ # * success: <tt>options[:login_good]</tt> or
319
+ # <tt>session[:site_return]</tt> or the realm
320
+ # * <tt>session[:openid]</tt> is cleared
321
+ # * <tt>session[:openid]['authenticated']</tt> is <tt>true</tt>
322
+ # * <tt>session[:openid]['identity']</tt> is the actual identifier
323
+ # * <tt>session[:openid]['identifier']</tt> is the pretty identifier
324
+ # * cancel: <tt>options[:login_good]</tt> or
325
+ # <tt>session[:site_return]</tt> or the realm
326
+ # * <tt>session[:openid]</tt> is cleared
327
+ # * <tt>session[:openid]['authenticated']</tt> is <tt>false</tt>
328
+ # * setup_needed: resubmits the authentication request. A flag is set for
329
+ # non-immediate handling.
330
+ # * <tt>session[:openid][:setup_needed]</tt> is set to <tt>true</tt>,
331
+ # which will prevent immediate style openid authentication.
332
+ def finish(consumer, session, req)
333
+ oid = consumer.complete(req.params, req.url)
334
+ pp oid if $DEBUG
335
+ req.env['rack.auth.openid.response'] = oid
336
+
337
+ goto = session.fetch :site_return, @realm
338
+ body = []
339
+
340
+ case oid.status
341
+ when ::OpenID::Consumer::FAILURE
342
+ session.clear
343
+ session['authenticated'] = false
344
+ req.env['rack.errors'].puts oid.message
345
+
346
+ goto = @options[:login_fail] if @option.key? :login_fail
347
+ body << "Authentication unsuccessful.\n"
348
+ when ::OpenID::Consumer::SUCCESS
349
+ session.clear
350
+
351
+ ## Extension support
352
+ extensions.each do |ext, args|
353
+ session[ext::NS_URI] = ext::Response.
354
+ from_success_response(oid).
355
+ get_extension_args
356
+ end
357
+
358
+ session['authenticated'] = true
359
+ # Value for unique identification and such
360
+ session['identity'] = oid.identity_url
361
+ # Value for display and UI labels
362
+ session['identifier'] = oid.display_identifier
363
+
364
+ goto = @options[:login_good] if @options.key? :login_good
365
+ body << "Authentication successful.\n"
366
+ when ::OpenID::Consumer::CANCEL
367
+ session.clear
368
+ session['authenticated'] = false
369
+
370
+ goto = @options[:login_fail] if @option.key? :login_fail
371
+ body << "Authentication cancelled.\n"
372
+ when ::OpenID::Consumer::SETUP_NEEDED
373
+ session[:setup_needed] = true
374
+ unless o_id = session[:openid_param]
375
+ raise('Required values missing.')
376
+ end
377
+
378
+ goto = req.script_name+
379
+ '?'+@options[:openid_param]+
380
+ '='+o_id
381
+ body << "Reauthentication required.\n"
382
+ end
383
+ body << oid.message if oid.message
384
+ [ 303, {'Location'=>goto}, body]
100
385
  end
101
- def auth_fail
102
- @options.
103
- fetch :auth_fail, [500, {'Content-Type'=>'text/plain'},'Foreign server failure.']
386
+
387
+ # The first argument should be the main extension module.
388
+ # The extension module should contain the constants:
389
+ # * class Request, with OpenID::Extension as an ancestor
390
+ # * class Response, with OpenID::Extension as an ancestor
391
+ # * string NS_URI, which defines the namespace of the extension, should
392
+ # be an absolute http uri
393
+ #
394
+ # All trailing arguments will be passed to extension::Request.new in
395
+ # #check.
396
+ # The openid response will be passed to
397
+ # extension::Response#from_success_response, #get_extension_args will be
398
+ # called on the result to attain the gathered data.
399
+ #
400
+ # This method returns the key at which the response data will be found in
401
+ # the session, which is the namespace uri by default.
402
+ def add_extension ext, *args
403
+ if not ext.is_a? Module
404
+ raise TypeError, "#{ext.inspect} is not a module"
405
+ elsif not (m = %w'Request Response NS_URI' - ext.constants).empty?
406
+ raise ArgumentError, "#{ext.inspect} missing #{m*', '}"
407
+ end
408
+
409
+ consts = [ext::Request, ext::Response]
410
+
411
+ if not consts.all?{|c| c.is_a? Class }
412
+ raise TypeError, "#{ext.inspect}'s Request or Response is not a class"
413
+ elsif not consts.all?{|c| ::OpenID::Extension > c }
414
+ raise ArgumentError, "#{ext.inspect}'s Request or Response not a decendant of OpenID::Extension"
415
+ end
416
+
417
+ if not ext::NS_URI.is_a? String
418
+ raise TypeError, "#{ext.inspect}'s NS_URI is not a string"
419
+ elsif not uri = URI(ext::NS_URI)
420
+ raise ArgumentError, "#{ext.inspect}'s NS_URI is not a valid uri"
421
+ elsif not uri.scheme =~ /^https?$/
422
+ raise ArgumentError, "#{ext.inspect}'s NS_URI is not an http uri"
423
+ elsif not uri.absolute?
424
+ raise ArgumentError, "#{ext.inspect}'s NS_URI is not and absolute uri"
425
+ end
426
+ @extensions[ext] = args
427
+ return ext::NS_URI
104
428
  end
105
- def bad_login
106
- @options.
107
- fetch :bad_login, [401, {'Content-Type'=>'text/plain'},'Identification has failed.']
429
+
430
+ # A conveniance method that returns the namespace of all current
431
+ # extensions used by this instance.
432
+ def extension_namespaces
433
+ @extensions.keys.map{|e|e::NS_URI}
108
434
  end
109
435
  end
110
436
  end