rack 0.9.1 → 1.0.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.
- data/COPYING +1 -1
- data/RDOX +115 -16
- data/README +54 -7
- data/Rakefile +61 -85
- data/SPEC +50 -17
- data/bin/rackup +9 -5
- data/example/protectedlobster.ru +1 -1
- data/lib/rack.rb +7 -3
- data/lib/rack/auth/abstract/handler.rb +13 -4
- data/lib/rack/auth/digest/md5.rb +1 -1
- data/lib/rack/auth/digest/request.rb +2 -2
- data/lib/rack/auth/openid.rb +344 -302
- data/lib/rack/builder.rb +1 -5
- data/lib/rack/chunked.rb +49 -0
- data/lib/rack/conditionalget.rb +4 -0
- data/lib/rack/content_length.rb +7 -3
- data/lib/rack/content_type.rb +23 -0
- data/lib/rack/deflater.rb +83 -74
- data/lib/rack/directory.rb +5 -2
- data/lib/rack/file.rb +4 -1
- data/lib/rack/handler.rb +22 -1
- data/lib/rack/handler/cgi.rb +7 -3
- data/lib/rack/handler/fastcgi.rb +26 -24
- data/lib/rack/handler/lsws.rb +7 -4
- data/lib/rack/handler/mongrel.rb +5 -3
- data/lib/rack/handler/scgi.rb +5 -3
- data/lib/rack/handler/thin.rb +3 -0
- data/lib/rack/handler/webrick.rb +11 -5
- data/lib/rack/lint.rb +138 -66
- data/lib/rack/lock.rb +16 -0
- data/lib/rack/mime.rb +4 -4
- data/lib/rack/mock.rb +3 -3
- data/lib/rack/reloader.rb +88 -46
- data/lib/rack/request.rb +46 -10
- data/lib/rack/response.rb +15 -3
- data/lib/rack/rewindable_input.rb +98 -0
- data/lib/rack/session/abstract/id.rb +71 -82
- data/lib/rack/session/cookie.rb +2 -0
- data/lib/rack/session/memcache.rb +59 -47
- data/lib/rack/session/pool.rb +56 -29
- data/lib/rack/showexceptions.rb +2 -1
- data/lib/rack/showstatus.rb +1 -1
- data/lib/rack/urlmap.rb +12 -5
- data/lib/rack/utils.rb +115 -65
- data/rack.gemspec +54 -0
- data/test/multipart/binary +0 -0
- data/test/multipart/empty +10 -0
- data/test/multipart/ie +6 -0
- data/test/multipart/nested +10 -0
- data/test/multipart/none +9 -0
- data/test/multipart/text +10 -0
- data/test/spec_rack_auth_basic.rb +5 -1
- data/test/spec_rack_auth_digest.rb +93 -36
- data/test/spec_rack_auth_openid.rb +47 -100
- data/test/spec_rack_builder.rb +2 -2
- data/test/spec_rack_chunked.rb +62 -0
- data/test/spec_rack_conditionalget.rb +7 -7
- data/test/spec_rack_content_type.rb +30 -0
- data/test/spec_rack_deflater.rb +36 -14
- data/test/spec_rack_directory.rb +1 -1
- data/test/spec_rack_file.rb +11 -0
- data/test/spec_rack_handler.rb +21 -2
- data/test/spec_rack_lint.rb +163 -44
- data/test/spec_rack_lock.rb +38 -0
- data/test/spec_rack_mock.rb +6 -1
- data/test/spec_rack_request.rb +81 -12
- data/test/spec_rack_response.rb +46 -2
- data/test/spec_rack_rewindable_input.rb +118 -0
- data/test/spec_rack_session_memcache.rb +170 -62
- data/test/spec_rack_session_pool.rb +129 -41
- data/test/spec_rack_static.rb +2 -2
- data/test/spec_rack_thin.rb +3 -2
- data/test/spec_rack_urlmap.rb +10 -0
- data/test/spec_rack_utils.rb +214 -49
- data/test/spec_rack_webrick.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
- data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
- metadata +95 -6
- data/AUTHORS +0 -8
data/SPEC
CHANGED
@@ -34,7 +34,9 @@ below.
|
|
34
34
|
within the application. This may be an
|
35
35
|
empty string, if the request URL targets
|
36
36
|
the application root and does not have a
|
37
|
-
trailing slash.
|
37
|
+
trailing slash. This value may be
|
38
|
+
percent-encoded when I originating from
|
39
|
+
a URL.
|
38
40
|
<tt>QUERY_STRING</tt>:: The portion of the request URL that
|
39
41
|
follows the <tt>?</tt>, if any. May be
|
40
42
|
empty, but is always required!
|
@@ -50,18 +52,27 @@ below.
|
|
50
52
|
request.
|
51
53
|
In addition to this, the Rack environment must include these
|
52
54
|
Rack-specific variables:
|
53
|
-
<tt>rack.version</tt>:: The Array [0
|
55
|
+
<tt>rack.version</tt>:: The Array [1,0], representing this version of Rack.
|
54
56
|
<tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
|
55
57
|
<tt>rack.input</tt>:: See below, the input stream.
|
56
58
|
<tt>rack.errors</tt>:: See below, the error stream.
|
57
59
|
<tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
|
58
60
|
<tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
|
59
61
|
<tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
|
62
|
+
Additional environment specifications have approved to
|
63
|
+
standardized middleware APIs. None of these are required to
|
64
|
+
be implemented by the server.
|
65
|
+
<tt>rack.session</tt>:: A hash like interface for storing request session data.
|
66
|
+
The store must implement:
|
67
|
+
store(key, value) (aliased as []=);
|
68
|
+
fetch(key, default = nil) (aliased as []);
|
69
|
+
delete(key);
|
70
|
+
clear;
|
60
71
|
The server or the application can store their own data in the
|
61
72
|
environment, too. The keys must contain at least one dot,
|
62
73
|
and should be prefixed uniquely. The prefix <tt>rack.</tt>
|
63
|
-
is reserved for use with the Rack core distribution and
|
64
|
-
not be used otherwise.
|
74
|
+
is reserved for use with the Rack core distribution and other
|
75
|
+
accepted specifications and must not be used otherwise.
|
65
76
|
The environment must not contain the keys
|
66
77
|
<tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
|
67
78
|
(use the versions without <tt>HTTP_</tt>).
|
@@ -80,12 +91,26 @@ There are the following restrictions:
|
|
80
91
|
<tt>SCRIPT_NAME</tt> is empty.
|
81
92
|
<tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
|
82
93
|
=== The Input Stream
|
83
|
-
The input stream
|
94
|
+
The input stream is an IO-like object which contains the raw HTTP
|
95
|
+
POST data. If it is a file then it must be opened in binary mode.
|
96
|
+
The input stream must respond to +gets+, +each+, +read+ and +rewind+.
|
84
97
|
* +gets+ must be called without arguments and return a string,
|
85
98
|
or +nil+ on EOF.
|
86
|
-
* +read+
|
87
|
-
|
99
|
+
* +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
|
100
|
+
If given, +length+ must be an non-negative Integer (>= 0) or +nil+, and +buffer+ must
|
101
|
+
be a String and may not be nil. If +length+ is given and not nil, then this method
|
102
|
+
reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
|
103
|
+
then this method reads all data until EOF.
|
104
|
+
When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
|
105
|
+
if +length+ is not given or is nil.
|
106
|
+
If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
|
107
|
+
newly created String object.
|
88
108
|
* +each+ must be called without arguments and only yield Strings.
|
109
|
+
* +rewind+ must be called without arguments. It rewinds the input
|
110
|
+
stream back to the beginning. It must not raise Errno::ESPIPE:
|
111
|
+
that is, it may not be a pipe or a socket. Therefore, handler
|
112
|
+
developers must buffer the input data into some rewindable object
|
113
|
+
if the underlying input stream is not rewindable.
|
89
114
|
* +close+ must never be called on the input stream.
|
90
115
|
=== The Error Stream
|
91
116
|
The error stream must respond to +puts+, +write+ and +flush+.
|
@@ -96,30 +121,38 @@ The error stream must respond to +puts+, +write+ and +flush+.
|
|
96
121
|
* +close+ must never be called on the error stream.
|
97
122
|
== The Response
|
98
123
|
=== The Status
|
99
|
-
|
124
|
+
This is an HTTP status. When parsed as integer (+to_i+), it must be
|
125
|
+
greater than or equal to 100.
|
100
126
|
=== The Headers
|
101
|
-
The header must respond to each
|
127
|
+
The header must respond to +each+, and yield values of key and value.
|
102
128
|
The header keys must be Strings.
|
103
129
|
The header must not contain a +Status+ key,
|
104
130
|
contain keys with <tt>:</tt> or newlines in their name,
|
105
131
|
contain keys names that end in <tt>-</tt> or <tt>_</tt>,
|
106
132
|
but only contain keys that consist of
|
107
133
|
letters, digits, <tt>_</tt> or <tt>-</tt> and start with a letter.
|
108
|
-
The values of the header must
|
109
|
-
|
110
|
-
|
134
|
+
The values of the header must be Strings,
|
135
|
+
consisting of lines (for multiple header values, e.g. multiple
|
136
|
+
<tt>Set-Cookie</tt> values) seperated by "\n".
|
137
|
+
The lines must not contain characters below 037.
|
111
138
|
=== The Content-Type
|
112
139
|
There must be a <tt>Content-Type</tt>, except when the
|
113
140
|
+Status+ is 1xx, 204 or 304, in which case there must be none
|
114
141
|
given.
|
115
142
|
=== The Content-Length
|
116
|
-
There must be a <tt>Content-Length</tt
|
117
|
-
+Status+ is 1xx, 204 or 304
|
118
|
-
given.
|
143
|
+
There must not be a <tt>Content-Length</tt> header when the
|
144
|
+
+Status+ is 1xx, 204 or 304.
|
119
145
|
=== The Body
|
120
|
-
The Body must respond to
|
146
|
+
The Body must respond to +each+
|
121
147
|
and must only yield String values.
|
122
|
-
|
148
|
+
The Body itself should not be an instance of String, as this will
|
149
|
+
break in Ruby 1.9.
|
150
|
+
If the Body responds to +close+, it will be called after iteration.
|
151
|
+
If the Body responds to +to_path+, it must return a String
|
152
|
+
identifying the location of a file whose contents are identical
|
153
|
+
to that produced by calling +each+; this may be used by the
|
154
|
+
server as an alternative, possibly more efficient way to
|
155
|
+
transport the response.
|
123
156
|
The Body commonly is an Array of Strings, the application
|
124
157
|
instance itself, or a File-like object.
|
125
158
|
== Thanks
|
data/bin/rackup
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# -*- ruby -*-
|
3
3
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path("#{__FILE__}/../../lib")
|
5
|
+
autoload :Rack, 'rack'
|
6
|
+
|
4
7
|
require 'optparse'
|
5
8
|
|
6
9
|
automatic = false
|
@@ -10,6 +13,10 @@ daemonize = false
|
|
10
13
|
pid = nil
|
11
14
|
options = {:Port => 9292, :Host => "0.0.0.0", :AccessLog => []}
|
12
15
|
|
16
|
+
# Don't evaluate CGI ISINDEX parameters.
|
17
|
+
# http://hoohoo.ncsa.uiuc.edu/cgi/cl.html
|
18
|
+
ARGV.clear if ENV.include?("REQUEST_METHOD")
|
19
|
+
|
13
20
|
opts = OptionParser.new("", 24, ' ') { |opts|
|
14
21
|
opts.banner = "Usage: rackup [ruby options] [rack options] [rackup config]"
|
15
22
|
|
@@ -74,7 +81,6 @@ opts = OptionParser.new("", 24, ' ') { |opts|
|
|
74
81
|
end
|
75
82
|
|
76
83
|
opts.on_tail("--version", "Show version") do
|
77
|
-
require 'rack'
|
78
84
|
puts "Rack #{Rack.version}"
|
79
85
|
exit
|
80
86
|
end
|
@@ -94,11 +100,9 @@ if config =~ /\.ru$/
|
|
94
100
|
if cfgfile[/^#\\(.*)/]
|
95
101
|
opts.parse! $1.split(/\s+/)
|
96
102
|
end
|
97
|
-
require 'rack'
|
98
103
|
inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app",
|
99
104
|
nil, config
|
100
105
|
else
|
101
|
-
require 'rack'
|
102
106
|
require config
|
103
107
|
inner_app = Object.const_get(File.basename(config, '.rb').capitalize)
|
104
108
|
end
|
@@ -127,7 +131,7 @@ p server if $DEBUG
|
|
127
131
|
case env
|
128
132
|
when "development"
|
129
133
|
app = Rack::Builder.new {
|
130
|
-
use Rack::CommonLogger,
|
134
|
+
use Rack::CommonLogger, $stderr unless server.name =~ /CGI/
|
131
135
|
use Rack::ShowExceptions
|
132
136
|
use Rack::Lint
|
133
137
|
run inner_app
|
@@ -135,7 +139,7 @@ when "development"
|
|
135
139
|
|
136
140
|
when "deployment"
|
137
141
|
app = Rack::Builder.new {
|
138
|
-
use Rack::CommonLogger,
|
142
|
+
use Rack::CommonLogger, $stderr unless server.name =~ /CGI/
|
139
143
|
run inner_app
|
140
144
|
}.to_app
|
141
145
|
|
data/example/protectedlobster.ru
CHANGED
data/lib/rack.rb
CHANGED
@@ -3,7 +3,8 @@
|
|
3
3
|
# Rack is freely distributable under the terms of an MIT-style license.
|
4
4
|
# See COPYING or http://www.opensource.org/licenses/mit-license.php.
|
5
5
|
|
6
|
-
|
6
|
+
path = File.expand_path(File.dirname(__FILE__))
|
7
|
+
$:.unshift(path) unless $:.include?(path)
|
7
8
|
|
8
9
|
|
9
10
|
# The Rack main module, serving as a namespace for all core Rack
|
@@ -14,7 +15,7 @@ $: << File.expand_path(File.dirname(__FILE__))
|
|
14
15
|
|
15
16
|
module Rack
|
16
17
|
# The Rack protocol version number implemented.
|
17
|
-
VERSION = [0
|
18
|
+
VERSION = [1,0]
|
18
19
|
|
19
20
|
# Return the Rack protocol version as a dotted string.
|
20
21
|
def self.version
|
@@ -23,14 +24,16 @@ module Rack
|
|
23
24
|
|
24
25
|
# Return the Rack release as a dotted string.
|
25
26
|
def self.release
|
26
|
-
"0
|
27
|
+
"1.0"
|
27
28
|
end
|
28
29
|
|
29
30
|
autoload :Builder, "rack/builder"
|
30
31
|
autoload :Cascade, "rack/cascade"
|
32
|
+
autoload :Chunked, "rack/chunked"
|
31
33
|
autoload :CommonLogger, "rack/commonlogger"
|
32
34
|
autoload :ConditionalGet, "rack/conditionalget"
|
33
35
|
autoload :ContentLength, "rack/content_length"
|
36
|
+
autoload :ContentType, "rack/content_type"
|
34
37
|
autoload :File, "rack/file"
|
35
38
|
autoload :Deflater, "rack/deflater"
|
36
39
|
autoload :Directory, "rack/directory"
|
@@ -38,6 +41,7 @@ module Rack
|
|
38
41
|
autoload :Handler, "rack/handler"
|
39
42
|
autoload :Head, "rack/head"
|
40
43
|
autoload :Lint, "rack/lint"
|
44
|
+
autoload :Lock, "rack/lock"
|
41
45
|
autoload :MethodOverride, "rack/methodoverride"
|
42
46
|
autoload :Mime, "rack/mime"
|
43
47
|
autoload :Recursive, "rack/recursive"
|
@@ -8,19 +8,28 @@ module Rack
|
|
8
8
|
|
9
9
|
attr_accessor :realm
|
10
10
|
|
11
|
-
def initialize(app, &authenticator)
|
12
|
-
@app, @authenticator = app, authenticator
|
11
|
+
def initialize(app, realm=nil, &authenticator)
|
12
|
+
@app, @realm, @authenticator = app, realm, authenticator
|
13
13
|
end
|
14
14
|
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def unauthorized(www_authenticate = challenge)
|
19
|
-
return [ 401,
|
19
|
+
return [ 401,
|
20
|
+
{ 'Content-Type' => 'text/plain',
|
21
|
+
'Content-Length' => '0',
|
22
|
+
'WWW-Authenticate' => www_authenticate.to_s },
|
23
|
+
[]
|
24
|
+
]
|
20
25
|
end
|
21
26
|
|
22
27
|
def bad_request
|
23
|
-
[ 400,
|
28
|
+
return [ 400,
|
29
|
+
{ 'Content-Type' => 'text/plain',
|
30
|
+
'Content-Length' => '0' },
|
31
|
+
[]
|
32
|
+
]
|
24
33
|
end
|
25
34
|
|
26
35
|
end
|
data/lib/rack/auth/digest/md5.rb
CHANGED
@@ -8,7 +8,7 @@ module Rack
|
|
8
8
|
class Request < Auth::AbstractRequest
|
9
9
|
|
10
10
|
def method
|
11
|
-
@env['REQUEST_METHOD']
|
11
|
+
@env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD']
|
12
12
|
end
|
13
13
|
|
14
14
|
def digest?
|
@@ -16,7 +16,7 @@ module Rack
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def correct_uri?
|
19
|
-
@env['PATH_INFO'] == uri
|
19
|
+
(@env['SCRIPT_NAME'].to_s + @env['PATH_INFO'].to_s) == uri
|
20
20
|
end
|
21
21
|
|
22
22
|
def nonce
|
data/lib/rack/auth/openid.rb
CHANGED
@@ -1,18 +1,30 @@
|
|
1
1
|
# AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
|
2
2
|
|
3
3
|
gem 'ruby-openid', '~> 2' if defined? Gem
|
4
|
-
require 'rack/
|
5
|
-
require '
|
6
|
-
require '
|
4
|
+
require 'rack/request'
|
5
|
+
require 'rack/utils'
|
6
|
+
require 'rack/auth/abstract/handler'
|
7
|
+
require 'uri'
|
7
8
|
require 'openid' #gem
|
8
9
|
require 'openid/extension' #gem
|
9
10
|
require 'openid/store/memory' #gem
|
10
11
|
|
11
12
|
module Rack
|
13
|
+
class Request
|
14
|
+
def openid_request
|
15
|
+
@env['rack.auth.openid.request']
|
16
|
+
end
|
17
|
+
|
18
|
+
def openid_response
|
19
|
+
@env['rack.auth.openid.response']
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
12
23
|
module Auth
|
13
|
-
|
14
|
-
#
|
15
|
-
#
|
24
|
+
|
25
|
+
# Rack::Auth::OpenID provides a simple method for setting up an OpenID
|
26
|
+
# Consumer. It requires the ruby-openid library from janrain to operate,
|
27
|
+
# as well as a rack method of session management.
|
16
28
|
#
|
17
29
|
# The ruby-openid home page is at http://openidenabled.com/ruby-openid/.
|
18
30
|
#
|
@@ -26,61 +38,26 @@ module Rack
|
|
26
38
|
# It is recommended to read through the OpenID spec, as well as
|
27
39
|
# ruby-openid's documentation, to understand what exactly goes on. However
|
28
40
|
# a setup as simple as the presented examples is enough to provide
|
29
|
-
# functionality.
|
41
|
+
# Consumer functionality.
|
30
42
|
#
|
31
43
|
# This library strongly intends to utilize the OpenID 2.0 features of the
|
32
|
-
# ruby-openid library,
|
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.
|
44
|
+
# ruby-openid library, which provides OpenID 1.0 compatiblity.
|
41
45
|
#
|
42
46
|
# NOTE: Due to the amount of data that this library stores in the
|
43
47
|
# session, Rack::Session::Cookie may fault.
|
44
|
-
|
48
|
+
|
49
|
+
class OpenID
|
50
|
+
|
45
51
|
class NoSession < RuntimeError; end
|
52
|
+
class BadExtension < RuntimeError; end
|
46
53
|
# Required for ruby-openid
|
47
|
-
|
48
|
-
HTML = '<html><head><title>%s</title></head><body>%s</body></html>'
|
54
|
+
ValidStatus = [:success, :setup_needed, :cancel, :failure]
|
49
55
|
|
50
|
-
# A Hash of options is taken as it's single initializing
|
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
56
|
# = Arguments
|
75
57
|
#
|
76
58
|
# 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.
|
59
|
+
# with their identity. This is required, also treated as the trust_root
|
60
|
+
# in OpenID 1.x exchanges.
|
84
61
|
#
|
85
62
|
# The optional second argument is a hash of options.
|
86
63
|
#
|
@@ -89,8 +66,8 @@ module Rack
|
|
89
66
|
# <tt>:return_to</tt> defines the url to return to after the client
|
90
67
|
# authenticates with the openid service provider. This url should point
|
91
68
|
# to where Rack::Auth::OpenID is mounted. If <tt>:return_to</tt> is not
|
92
|
-
# provided,
|
93
|
-
#
|
69
|
+
# provided, return_to will be the current url which allows flexibility
|
70
|
+
# with caveats.
|
94
71
|
#
|
95
72
|
# <tt>:session_key</tt> defines the key to the session hash in the env.
|
96
73
|
# It defaults to 'rack.session'.
|
@@ -99,297 +76,229 @@ module Rack
|
|
99
76
|
# find the identifier to resolve. As per the 2.0 spec, the default is
|
100
77
|
# 'openid_identifier'.
|
101
78
|
#
|
102
|
-
# <tt>:
|
103
|
-
#
|
79
|
+
# <tt>:store</tt> defined what OpenID Store to use for persistant
|
80
|
+
# information. By default a Store::Memory will be used.
|
104
81
|
#
|
105
|
-
#
|
82
|
+
# <tt>:immediate</tt> as true will make initial requests to be of an
|
83
|
+
# immediate type. This is false by default. See OpenID specification
|
84
|
+
# documentation.
|
106
85
|
#
|
107
|
-
# <tt>:
|
108
|
-
#
|
86
|
+
# <tt>:extensions</tt> should be a hash of openid extension
|
87
|
+
# implementations. The key should be the extension main module, the value
|
88
|
+
# should be an array of arguments for extension::Request.new.
|
89
|
+
# The hash is iterated over and passed to #add_extension for processing.
|
90
|
+
# Please see #add_extension for further documentation.
|
109
91
|
#
|
110
|
-
#
|
111
|
-
# process has failed.
|
92
|
+
# == Examples
|
112
93
|
#
|
113
|
-
#
|
114
|
-
# process
|
115
|
-
# has been cancelled.
|
94
|
+
# simple_oid = OpenID.new('http://mysite.com/')
|
116
95
|
#
|
117
|
-
#
|
96
|
+
# return_oid = OpenID.new('http://mysite.com/', {
|
97
|
+
# :return_to => 'http://mysite.com/openid'
|
98
|
+
# })
|
118
99
|
#
|
119
|
-
#
|
120
|
-
#
|
100
|
+
# complex_oid = OpenID.new('http://mysite.com/',
|
101
|
+
# :immediate => true,
|
102
|
+
# :extensions => {
|
103
|
+
# ::OpenID::SReg => [['email'],['nickname']]
|
104
|
+
# }
|
105
|
+
# )
|
121
106
|
#
|
122
|
-
#
|
123
|
-
# OpenID::DiscoveryFailure occurs. This is typically due to being unable
|
124
|
-
# to access the identity url or identity server.
|
107
|
+
# = Advanced
|
125
108
|
#
|
126
|
-
#
|
127
|
-
#
|
109
|
+
# Most of the functionality of this library is encapsulated such that
|
110
|
+
# expansion and overriding functions isn't difficult nor tricky.
|
111
|
+
# Alternately, to avoid opening up singleton objects or subclassing, a
|
112
|
+
# wrapper rack middleware can be composed to act upon Auth::OpenID's
|
113
|
+
# responses. See #check and #finish for locations of pertinent data.
|
128
114
|
#
|
129
|
-
#
|
115
|
+
# == Responses
|
130
116
|
#
|
131
|
-
#
|
132
|
-
#
|
133
|
-
#
|
117
|
+
# To change the responses that Auth::OpenID returns, override the methods
|
118
|
+
# #redirect, #bad_request, #unauthorized, #access_denied, and
|
119
|
+
# #foreign_server_failure.
|
134
120
|
#
|
135
|
-
#
|
136
|
-
#
|
121
|
+
# Additionally #confirm_post_params is used when the URI would exceed
|
122
|
+
# length limits on a GET request when doing the initial verification
|
123
|
+
# request.
|
124
|
+
#
|
125
|
+
# == Processing
|
126
|
+
#
|
127
|
+
# To change methods of processing completed transactions, override the
|
128
|
+
# methods #success, #setup_needed, #cancel, and #failure. Please ensure
|
129
|
+
# the returned object is a rack compatible response.
|
130
|
+
#
|
131
|
+
# The first argument is an OpenID::Response, the second is a
|
132
|
+
# Rack::Request of the current request, the last is the hash used in
|
133
|
+
# ruby-openid handling, which can be found manually at
|
134
|
+
# env['rack.session'][:openid].
|
135
|
+
#
|
136
|
+
# This is useful if you wanted to expand the processing done, such as
|
137
|
+
# setting up user accounts.
|
138
|
+
#
|
139
|
+
# oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
|
140
|
+
# def oid_app.success oid, request, session
|
141
|
+
# user = Models::User[oid.identity_url]
|
142
|
+
# user ||= Models::User.create_from_openid oid
|
143
|
+
# request['rack.session'][:user] = user.id
|
144
|
+
# redirect MyApp.site_home
|
145
|
+
# end
|
146
|
+
#
|
147
|
+
# site_map['/openid'] = oid_app
|
148
|
+
# map = Rack::URLMap.new site_map
|
149
|
+
# ...
|
150
|
+
|
137
151
|
def initialize(realm, options={})
|
138
|
-
@realm = realm
|
139
152
|
realm = URI(realm)
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
153
|
+
raise ArgumentError, "Invalid realm: #{realm}" \
|
154
|
+
unless realm.absolute? \
|
155
|
+
and realm.fragment.nil? \
|
156
|
+
and realm.scheme =~ /^https?$/ \
|
157
|
+
and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/
|
158
|
+
realm.path = '/' if realm.path.empty?
|
159
|
+
@realm = realm.to_s
|
160
|
+
|
161
|
+
if ruri = options[:return_to]
|
162
|
+
ruri = URI(ruri)
|
163
|
+
raise ArgumentError, "Invalid return_to: #{ruri}" \
|
164
|
+
unless ruri.absolute? \
|
165
|
+
and ruri.scheme =~ /^https?$/ \
|
166
|
+
and ruri.fragment.nil?
|
167
|
+
raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \
|
168
|
+
unless self.within_realm?(ruri)
|
169
|
+
@return_to = ruri.to_s
|
144
170
|
end
|
145
171
|
|
146
|
-
[:
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
172
|
+
@session_key = options[:session_key] || 'rack.session'
|
173
|
+
@openid_param = options[:openid_param] || 'openid_identifier'
|
174
|
+
@store = options[:store] || ::OpenID::Store::Memory.new
|
175
|
+
@immediate = !!options[:immediate]
|
161
176
|
|
162
|
-
|
177
|
+
@extensions = {}
|
163
178
|
if extensions = options.delete(:extensions)
|
164
179
|
extensions.each do |ext, args|
|
165
180
|
add_extension ext, *args
|
166
181
|
end
|
167
182
|
end
|
168
183
|
|
169
|
-
|
170
|
-
|
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
|
178
|
-
}.merge(options)
|
179
|
-
@extensions = {}
|
184
|
+
# Undocumented, semi-experimental
|
185
|
+
@anonymous = !!options[:anonymous]
|
180
186
|
end
|
181
187
|
|
182
|
-
attr_reader :
|
188
|
+
attr_reader :realm, :return_to, :session_key, :openid_param, :store,
|
189
|
+
:immediate, :extensions
|
183
190
|
|
184
|
-
#
|
185
|
-
#
|
186
|
-
#
|
191
|
+
# Sets up and uses session data at <tt>:openid</tt> within the session.
|
192
|
+
# Errors in this setup will raise a NoSession exception.
|
193
|
+
#
|
194
|
+
# If the parameter 'openid.mode' is set, which implies a followup from
|
195
|
+
# the openid server, processing is passed to #finish and the result is
|
196
|
+
# returned. However, if there is no appropriate openid information in the
|
197
|
+
# session, a 400 error is returned.
|
187
198
|
#
|
188
199
|
# If the parameter specified by <tt>options[:openid_param]</tt> is
|
189
200
|
# present, processing is passed to #check and the result is returned.
|
190
201
|
#
|
191
|
-
# If
|
192
|
-
|
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.
|
202
|
+
# If neither of these conditions are met, #unauthorized is called.
|
203
|
+
|
199
204
|
def call(env)
|
200
205
|
env['rack.auth.openid'] = self
|
201
|
-
|
202
|
-
unless
|
203
|
-
raise
|
206
|
+
env_session = env[@session_key]
|
207
|
+
unless env_session and env_session.is_a?(Hash)
|
208
|
+
raise NoSession, 'No compatible session'
|
204
209
|
end
|
205
210
|
# let us work in our own namespace...
|
206
|
-
session = (
|
207
|
-
unless session and session.is_a?
|
208
|
-
raise
|
211
|
+
session = (env_session[:openid] ||= {})
|
212
|
+
unless session and session.is_a?(Hash)
|
213
|
+
raise NoSession, 'Incompatible openid session'
|
209
214
|
end
|
210
215
|
|
211
|
-
request = Rack::Request.new
|
212
|
-
consumer = ::OpenID::Consumer.new
|
216
|
+
request = Rack::Request.new(env)
|
217
|
+
consumer = ::OpenID::Consumer.new(session, @store)
|
213
218
|
|
214
|
-
if request.
|
215
|
-
|
216
|
-
|
217
|
-
|
219
|
+
if mode = request.GET['openid.mode']
|
220
|
+
if session.key?(:openid_param)
|
221
|
+
finish(consumer, session, request)
|
222
|
+
else
|
223
|
+
bad_request
|
224
|
+
end
|
225
|
+
elsif request.GET[@openid_param]
|
226
|
+
check(consumer, session, request)
|
218
227
|
else
|
219
|
-
|
220
|
-
bad_request
|
221
|
-
end
|
222
|
-
rescue NoSession
|
223
|
-
env['rack.errors'].puts($!.message, *$@)
|
224
|
-
|
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($!)
|
228
|
+
unauthorized
|
234
229
|
end
|
235
|
-
@options.
|
236
|
-
fetch :error, [ 500,
|
237
|
-
{'Content-Type'=>'text/plain'},
|
238
|
-
'OpenID has encountered an error.' ]
|
239
230
|
end
|
240
231
|
|
241
232
|
# As the first part of OpenID consumer action, #check retrieves the data
|
242
233
|
# required for completion.
|
243
234
|
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
#
|
235
|
+
# If all parameters fit within the max length of a URI, a 303 redirect
|
236
|
+
# will be returned. Otherwise #confirm_post_params will be called.
|
237
|
+
#
|
238
|
+
# Any messages from OpenID's request are logged to env['rack.errors']
|
239
|
+
#
|
240
|
+
# <tt>env['rack.auth.openid.request']</tt> is the openid checkid request
|
241
|
+
# instance.
|
242
|
+
#
|
243
|
+
# <tt>session[:openid_param]</tt> is set to the openid identifier
|
244
|
+
# provided by the user.
|
245
|
+
#
|
246
|
+
# <tt>session[:return_to]</tt> is set to the return_to uri given to the
|
247
|
+
# identity provider.
|
248
|
+
|
250
249
|
def check(consumer, session, req)
|
251
|
-
|
252
|
-
oid = consumer.begin(session[:openid_param], @options[:anonymous])
|
253
|
-
pp oid if $DEBUG
|
250
|
+
oid = consumer.begin(req.GET[@openid_param], @anonymous)
|
254
251
|
req.env['rack.auth.openid.request'] = oid
|
255
|
-
|
256
|
-
|
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
|
252
|
+
req.env['rack.errors'].puts(oid.message)
|
253
|
+
p oid if $DEBUG
|
264
254
|
|
265
255
|
## Extension support
|
266
256
|
extensions.each do |ext,args|
|
267
|
-
oid.add_extension
|
257
|
+
oid.add_extension(ext::Request.new(*args))
|
268
258
|
end
|
269
259
|
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
260
|
+
session[:openid_param] = req.GET[openid_param]
|
261
|
+
return_to_uri = return_to ? return_to : req.url
|
262
|
+
session[:return_to] = return_to_uri
|
263
|
+
immediate = session.key?(:setup_needed) ? false : immediate
|
264
|
+
|
265
|
+
if oid.send_redirect?(realm, return_to_uri, immediate)
|
266
|
+
uri = oid.redirect_url(realm, return_to_uri, immediate)
|
267
|
+
redirect(uri)
|
277
268
|
else
|
278
|
-
|
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 ]
|
269
|
+
confirm_post_params(oid, realm, return_to_uri, immediate)
|
285
270
|
end
|
286
271
|
rescue ::OpenID::DiscoveryFailure => e
|
287
272
|
# thrown from inside OpenID::Consumer#begin by yadis stuff
|
288
|
-
req.env['rack.errors'].puts(
|
289
|
-
|
290
|
-
@options. ### Foreign server failed
|
291
|
-
fetch :auth_fail, [ 503,
|
292
|
-
{'Content-Type'=>'text/plain'},
|
293
|
-
'Foreign server failure.' ]
|
273
|
+
req.env['rack.errors'].puts([e.message, *e.backtrace]*"\n")
|
274
|
+
return foreign_server_failure
|
294
275
|
end
|
295
276
|
|
296
|
-
# This is the final portion of authentication.
|
297
|
-
#
|
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
|
-
#
|
277
|
+
# This is the final portion of authentication.
|
278
|
+
# If successful, a redirect to the realm is be returned.
|
307
279
|
# Data gathered from extensions are stored in session[:openid] with the
|
308
280
|
# extension's namespace uri as the key.
|
309
281
|
#
|
310
|
-
#
|
311
|
-
#
|
312
|
-
#
|
313
|
-
#
|
314
|
-
|
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.
|
282
|
+
# Any messages from OpenID's response are logged to env['rack.errors']
|
283
|
+
#
|
284
|
+
# <tt>env['rack.auth.openid.response']</tt> will contain the openid
|
285
|
+
# response.
|
286
|
+
|
332
287
|
def finish(consumer, session, req)
|
333
|
-
oid = consumer.complete(req.
|
334
|
-
pp oid if $DEBUG
|
288
|
+
oid = consumer.complete(req.GET, req.url)
|
335
289
|
req.env['rack.auth.openid.response'] = oid
|
290
|
+
req.env['rack.errors'].puts(oid.message)
|
291
|
+
p oid if $DEBUG
|
336
292
|
|
337
|
-
|
338
|
-
|
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 @options.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 @options.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]
|
293
|
+
raise unless ValidStatus.include?(oid.status)
|
294
|
+
__send__(oid.status, oid, req, session)
|
385
295
|
end
|
386
296
|
|
387
297
|
# The first argument should be the main extension module.
|
388
298
|
# The extension module should contain the constants:
|
389
|
-
# * class Request,
|
390
|
-
# * class Response,
|
391
|
-
# * string NS_URI, which
|
392
|
-
# be an absolute http uri
|
299
|
+
# * class Request, should have OpenID::Extension as an ancestor
|
300
|
+
# * class Response, should have OpenID::Extension as an ancestor
|
301
|
+
# * string NS_URI, which defining the namespace of the extension
|
393
302
|
#
|
394
303
|
# All trailing arguments will be passed to extension::Request.new in
|
395
304
|
# #check.
|
@@ -399,39 +308,172 @@ module Rack
|
|
399
308
|
#
|
400
309
|
# This method returns the key at which the response data will be found in
|
401
310
|
# the session, which is the namespace uri by default.
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
311
|
+
|
312
|
+
def add_extension(ext, *args)
|
313
|
+
raise BadExtension unless valid_extension?(ext)
|
314
|
+
extensions[ext] = args
|
315
|
+
return ext::NS_URI
|
316
|
+
end
|
317
|
+
|
318
|
+
# Checks the validitity, in the context of usage, of a submitted
|
319
|
+
# extension.
|
320
|
+
|
321
|
+
def valid_extension?(ext)
|
322
|
+
if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) }
|
323
|
+
raise ArgumentError, 'Extension is missing constants.'
|
324
|
+
elsif not ext::Response.respond_to?(:from_success_response)
|
325
|
+
raise ArgumentError, 'Response is missing required method.'
|
408
326
|
end
|
327
|
+
return true
|
328
|
+
rescue
|
329
|
+
return false
|
330
|
+
end
|
331
|
+
|
332
|
+
# Checks the provided uri to ensure it'd be considered within the realm.
|
333
|
+
# is currently not compatible with wildcard realms.
|
334
|
+
|
335
|
+
def within_realm? uri
|
336
|
+
uri = URI.parse(uri.to_s)
|
337
|
+
realm = URI.parse(self.realm)
|
338
|
+
return false unless uri.absolute?
|
339
|
+
return false unless uri.path[0, realm.path.size] == realm.path
|
340
|
+
return false unless uri.host == realm.host or realm.host[/^\*\./]
|
341
|
+
# for wildcard support, is awkward with URI limitations
|
342
|
+
realm_match = Regexp.escape(realm.host).
|
343
|
+
sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$'
|
344
|
+
return false unless uri.host.match(realm_match)
|
345
|
+
return true
|
346
|
+
end
|
347
|
+
alias_method :include?, :within_realm?
|
348
|
+
|
349
|
+
protected
|
350
|
+
|
351
|
+
### These methods define some of the boilerplate responses.
|
409
352
|
|
410
|
-
|
353
|
+
# Returns an html form page for posting to an Identity Provider if the
|
354
|
+
# GET request would exceed the upper URI length limit.
|
411
355
|
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
356
|
+
def confirm_post_params(oid, realm, return_to, immediate)
|
357
|
+
Rack::Response.new.finish do |r|
|
358
|
+
r.write '<html><head><title>Confirm...</title></head><body>'
|
359
|
+
r.write oid.form_markup(realm, return_to, immediate)
|
360
|
+
r.write '</body></html>'
|
416
361
|
end
|
362
|
+
end
|
363
|
+
|
364
|
+
# Returns a 303 redirect with the destination of that provided by the
|
365
|
+
# argument.
|
366
|
+
|
367
|
+
def redirect(uri)
|
368
|
+
[ 303, {'Content-Length'=>'0', 'Content-Type'=>'text/plain',
|
369
|
+
'Location' => uri},
|
370
|
+
[] ]
|
371
|
+
end
|
372
|
+
|
373
|
+
# Returns an empty 400 response.
|
417
374
|
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
375
|
+
def bad_request
|
376
|
+
[ 400, {'Content-Type'=>'text/plain', 'Content-Length'=>'0'},
|
377
|
+
[''] ]
|
378
|
+
end
|
379
|
+
|
380
|
+
# Returns a basic unauthorized 401 response.
|
381
|
+
|
382
|
+
def unauthorized
|
383
|
+
[ 401, {'Content-Type' => 'text/plain', 'Content-Length' => '13'},
|
384
|
+
['Unauthorized.'] ]
|
385
|
+
end
|
386
|
+
|
387
|
+
# Returns a basic access denied 403 response.
|
388
|
+
|
389
|
+
def access_denied
|
390
|
+
[ 403, {'Content-Type' => 'text/plain', 'Content-Length' => '14'},
|
391
|
+
['Access denied.'] ]
|
392
|
+
end
|
393
|
+
|
394
|
+
# Returns a 503 response to be used if communication with the remote
|
395
|
+
# OpenID server fails.
|
396
|
+
|
397
|
+
def foreign_server_failure
|
398
|
+
[ 503, {'Content-Type'=>'text/plain', 'Content-Length' => '23'},
|
399
|
+
['Foreign server failure.'] ]
|
400
|
+
end
|
401
|
+
|
402
|
+
private
|
403
|
+
|
404
|
+
### These methods are called after a transaction is completed, depending
|
405
|
+
# on its outcome. These should all return a rack compatible response.
|
406
|
+
# You'd want to override these to provide additional functionality.
|
407
|
+
|
408
|
+
# Called to complete processing on a successful transaction.
|
409
|
+
# Within the openid session, :openid_identity and :openid_identifier are
|
410
|
+
# set to the user friendly and the standard representation of the
|
411
|
+
# validated identity. All other data in the openid session is cleared.
|
412
|
+
|
413
|
+
def success(oid, request, session)
|
414
|
+
session.clear
|
415
|
+
session[:openid_identity] = oid.display_identifier
|
416
|
+
session[:openid_identifier] = oid.identity_url
|
417
|
+
extensions.keys.each do |ext|
|
418
|
+
label = ext.name[/[^:]+$/].downcase
|
419
|
+
response = ext::Response.from_success_response(oid)
|
420
|
+
session[label] = response.data
|
426
421
|
end
|
427
|
-
|
428
|
-
return ext::NS_URI
|
422
|
+
redirect(realm)
|
429
423
|
end
|
430
424
|
|
431
|
-
#
|
432
|
-
#
|
433
|
-
|
434
|
-
|
425
|
+
# Called if the Identity Provider indicates further setup by the user is
|
426
|
+
# required.
|
427
|
+
# The identifier is retrived from the openid session at :openid_param.
|
428
|
+
# And :setup_needed is set to true to prevent looping.
|
429
|
+
|
430
|
+
def setup_needed(oid, request, session)
|
431
|
+
identifier = session[:openid_param]
|
432
|
+
session[:setup_needed] = true
|
433
|
+
redirect req.script_name + '?' + openid_param + '=' + identifier
|
434
|
+
end
|
435
|
+
|
436
|
+
# Called if the user indicates they wish to cancel identification.
|
437
|
+
# Data within openid session is cleared.
|
438
|
+
|
439
|
+
def cancel(oid, request, session)
|
440
|
+
session.clear
|
441
|
+
access_denied
|
442
|
+
end
|
443
|
+
|
444
|
+
# Called if the Identity Provider indicates the user is unable to confirm
|
445
|
+
# their identity. Data within the openid session is left alone, in case
|
446
|
+
# of swarm auth attacks.
|
447
|
+
|
448
|
+
def failure(oid, request, session)
|
449
|
+
unauthorized
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
# A class developed out of the request to use OpenID as an authentication
|
454
|
+
# middleware. The request will be sent to the OpenID instance unless the
|
455
|
+
# block evaluates to true. For example in rackup, you can use it as such:
|
456
|
+
#
|
457
|
+
# use Rack::Session::Pool
|
458
|
+
# use Rack::Auth::OpenIDAuth, realm, openid_options do |env|
|
459
|
+
# env['rack.session'][:authkey] == a_string
|
460
|
+
# end
|
461
|
+
# run RackApp
|
462
|
+
#
|
463
|
+
# Or simply:
|
464
|
+
#
|
465
|
+
# app = Rack::Auth::OpenIDAuth.new app, realm, openid_options, &auth
|
466
|
+
|
467
|
+
class OpenIDAuth < Rack::Auth::AbstractHandler
|
468
|
+
attr_reader :oid
|
469
|
+
def initialize(app, realm, options={}, &auth)
|
470
|
+
@oid = OpenID.new(realm, options)
|
471
|
+
super(app, &auth)
|
472
|
+
end
|
473
|
+
|
474
|
+
def call(env)
|
475
|
+
to = auth.call(env) ? @app : @oid
|
476
|
+
to.call env
|
435
477
|
end
|
436
478
|
end
|
437
479
|
end
|