nov-rack-openid 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Joshua Peek
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,70 @@
1
+ = Rack::OpenID
2
+
3
+ Provides a more HTTPish API around the ruby-openid library.
4
+
5
+ === Usage
6
+
7
+ You trigger an OpenID request similar to HTTP authentication. From your app, return a "401 Unauthorized" and a "WWW-Authenticate" header with the identifier you would like to validate.
8
+
9
+ On competition, the OpenID response is automatically verified and assigned to
10
+ <tt>env["rack.openid.response"]</tt>.
11
+
12
+ === Rack Example
13
+
14
+ MyApp = lambda { |env|
15
+ if resp = env["rack.openid.response"]
16
+ case resp.status
17
+ when :success
18
+ ...
19
+ when :failure
20
+ ...
21
+ else
22
+ [401, {"WWW-Authenticate" => 'OpenID identifier="http://example.com/"'}, []]
23
+ end
24
+ }
25
+
26
+ use Rack::OpenID
27
+ run MyApp
28
+
29
+ === Sinatra Example
30
+
31
+ # Session needs to be before Rack::OpenID
32
+ use Rack::Session::Cookie
33
+
34
+ require 'rack/openid'
35
+ use Rack::OpenID
36
+
37
+ get '/login' do
38
+ erb :login
39
+ end
40
+
41
+ post '/login' do
42
+ if resp = request.env["rack.openid.response"]
43
+ if resp.status == :success
44
+ "Welcome: #{resp.display_identifier}"
45
+ else
46
+ "Error: #{resp.status}"
47
+ end
48
+ else
49
+ headers 'WWW-Authenticate' => Rack::OpenID.build_header(
50
+ :identifier => params["openid_identifier"]
51
+ )
52
+ throw :halt, [401, 'got openid?']
53
+ end
54
+ end
55
+
56
+ use_in_file_templates!
57
+
58
+ __END__
59
+
60
+ @@ login
61
+ <form action="/login" method="post">
62
+ <p>
63
+ <label for="openid_identifier">OpenID:</label>
64
+ <input id="openid_identifier" name="openid_identifier" type="text" />
65
+ </p>
66
+
67
+ <p>
68
+ <input name="commit" type="submit" value="Sign in" />
69
+ </p>
70
+ </form>
@@ -0,0 +1,65 @@
1
+ require 'rake/testtask'
2
+
3
+ task :default => :test
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.warning = true
7
+ end
8
+
9
+ begin
10
+ require 'jeweler'
11
+
12
+ class Jeweler
13
+ module Commands
14
+ class ReleaseGemspec
15
+ def run
16
+ unless clean_staging_area?
17
+ system "git status"
18
+ raise "Unclean staging area! Be sure to commit or .gitignore everything first. See `git status` above."
19
+ end
20
+ repo.checkout('releasable')
21
+ regenerate_gemspec!
22
+ commit_gemspec! if gemspec_changed?
23
+ output.puts "Pushing releasable to origin"
24
+ repo.push
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ class Jeweler
31
+ module Commands
32
+ class ReleaseToGit
33
+ def run
34
+ unless clean_staging_area?
35
+ system "git status"
36
+ raise "Unclean staging area! Be sure to commit or .gitignore everything first. See `git status` above."
37
+ end
38
+ repo.checkout('releasable')
39
+ repo.push
40
+ if release_not_tagged?
41
+ output.puts "Tagging #{release_tag}"
42
+ repo.add_tag(release_tag)
43
+ output.puts "Pushing #{release_tag} to origin"
44
+ repo.push('origin', release_tag)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ Jeweler::Tasks.new do |gem|
52
+ gem.name = 'nov-rack-openid'
53
+ gem.summary = %Q{Provides a more HTTPish API around the ruby-openid library}
54
+ gem.description = %Q{Provides a more HTTPish API around the ruby-openid library}
55
+ gem.email = 'nov@matake.jp'
56
+ gem.homepage = 'http://github.com/nov/rack-openid'
57
+ gem.authors = ['nov matake']
58
+ gem.add_dependency 'rack', '>= 1.1'
59
+ gem.add_dependency 'nov-ruby-openid', '>= 2.1.9'
60
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
61
+ end
62
+ Jeweler::GemcutterTasks.new
63
+ rescue LoadError
64
+ puts 'Jeweler not available. Install it with: gem install jeweler'
65
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.2.1
@@ -0,0 +1,2 @@
1
+ rack >=1.1.0
2
+ ruby-openid >=2.1.8
@@ -0,0 +1 @@
1
+ git://github.com/roman/rots.git
@@ -0,0 +1,283 @@
1
+ require 'rack/request'
2
+ require 'rack/utils'
3
+
4
+ require 'openid'
5
+ require 'openid/consumer'
6
+ require 'openid/extensions/sreg'
7
+ require 'openid/extensions/ax'
8
+ require 'openid/extensions/oauth'
9
+ require 'openid/extensions/ui'
10
+
11
+ module Rack #:nodoc:
12
+ # A Rack middleware that provides a more HTTPish API around the
13
+ # ruby-openid library.
14
+ #
15
+ # You trigger an OpenID request similar to HTTP authentication.
16
+ # From your app, return a "401 Unauthorized" and a "WWW-Authenticate"
17
+ # header with the identifier you would like to validate.
18
+ #
19
+ # On competition, the OpenID response is automatically verified and
20
+ # assigned to <tt>env["rack.openid.response"]</tt>.
21
+ class OpenID
22
+ # Helper method for building the "WWW-Authenticate" header value.
23
+ #
24
+ # Rack::OpenID.build_header(:identifier => "http://josh.openid.com/")
25
+ # #=> OpenID identifier="http://josh.openid.com/"
26
+ def self.build_header(params = {})
27
+ 'OpenID ' + params.map { |key, value|
28
+ if value.is_a?(Array)
29
+ "#{key}=\"#{value.join(',')}\""
30
+ else
31
+ "#{key}=\"#{value}\""
32
+ end
33
+ }.join(', ')
34
+ end
35
+
36
+ # Helper method for parsing "WWW-Authenticate" header values into
37
+ # a hash.
38
+ #
39
+ # Rack::OpenID.parse_header("OpenID identifier='http://josh.openid.com/'")
40
+ # #=> {:identifier => "http://josh.openid.com/"}
41
+ def self.parse_header(str)
42
+ params = {}
43
+ if str =~ AUTHENTICATE_REGEXP
44
+ str = str.gsub(/#{AUTHENTICATE_REGEXP}\s+/, '')
45
+ str.split(', ').each { |pair|
46
+ key, *value = pair.split('=')
47
+ value = value.join('=')
48
+ value.gsub!(/^\"/, '').gsub!(/\"$/, "")
49
+ value = value.split(',')
50
+ params[key] = value.length > 1 ? value : value.first
51
+ }
52
+ end
53
+ params
54
+ end
55
+
56
+ class TimeoutResponse #:nodoc:
57
+ include ::OpenID::Consumer::Response
58
+ STATUS = :failure
59
+ end
60
+
61
+ class MissingResponse #:nodoc:
62
+ include ::OpenID::Consumer::Response
63
+ STATUS = :missing
64
+ end
65
+
66
+ # :stopdoc:
67
+
68
+ HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
69
+
70
+ RESPONSE = "rack.openid.response"
71
+ AUTHENTICATE_HEADER = "WWW-Authenticate"
72
+ AUTHENTICATE_REGEXP = /^OpenID/
73
+
74
+ URL_FIELD_SELECTOR = lambda { |field| field.to_s =~ %r{^https?://} }
75
+
76
+ # :startdoc:
77
+
78
+ # Initialize middleware with application and optional OpenID::Store.
79
+ # If no store is given, OpenID::Store::Memory is used.
80
+ #
81
+ # use Rack::OpenID
82
+ #
83
+ # or
84
+ #
85
+ # use Rack::OpenID, OpenID::Store::Memcache.new
86
+ def initialize(app, store = nil)
87
+ @app = app
88
+ @store = store || default_store
89
+ end
90
+
91
+ # Standard Rack +call+ dispatch that accepts an +env+ and
92
+ # returns a <tt>[status, header, body]</tt> response.
93
+ def call(env)
94
+ req = Rack::Request.new(env)
95
+ if req.params["openid.mode"]
96
+ complete_authentication(env)
97
+ end
98
+
99
+ status, headers, body = @app.call(env)
100
+
101
+ qs = headers[AUTHENTICATE_HEADER]
102
+ if status.to_i == 401 && qs && qs.match(AUTHENTICATE_REGEXP)
103
+ begin_authentication(env, qs)
104
+ else
105
+ [status, headers, body]
106
+ end
107
+ end
108
+
109
+ private
110
+ def begin_authentication(env, qs)
111
+ req = Rack::Request.new(env)
112
+ params = self.class.parse_header(qs)
113
+ session = env["rack.session"]
114
+
115
+ unless session
116
+ raise RuntimeError, "Rack::OpenID requires a session"
117
+ end
118
+
119
+ consumer = ::OpenID::Consumer.new(session, @store)
120
+ identifier = params['identifier'] || params['identity']
121
+
122
+ begin
123
+ oidreq = consumer.begin(identifier)
124
+ add_simple_registration_fields(oidreq, params)
125
+ add_attribute_exchange_fields(oidreq, params)
126
+ add_oauth_fields(oidreq, params)
127
+ add_ui_fields(oidreq, params)
128
+ url = open_id_redirect_url(req, oidreq, params["trust_root"], params["return_to"], params["method"])
129
+ return redirect_to(url)
130
+ rescue ::OpenID::OpenIDError, Timeout::Error => e
131
+ env[RESPONSE] = MissingResponse.new
132
+ return @app.call(env)
133
+ end
134
+ end
135
+
136
+ def complete_authentication(env)
137
+ req = Rack::Request.new(env)
138
+ session = env["rack.session"]
139
+
140
+ unless session
141
+ raise RuntimeError, "Rack::OpenID requires a session"
142
+ end
143
+
144
+ oidresp = timeout_protection_from_identity_server {
145
+ consumer = ::OpenID::Consumer.new(session, @store)
146
+ consumer.complete(flatten_params(req.params), req.url)
147
+ }
148
+
149
+ env[RESPONSE] = oidresp
150
+
151
+ method = req.GET["_method"]
152
+ override_request_method(env, method)
153
+
154
+ sanitize_query_string(env)
155
+ end
156
+
157
+ def flatten_params(params)
158
+ Rack::Utils.parse_query(Rack::Utils.build_nested_query(params))
159
+ end
160
+
161
+ def override_request_method(env, method)
162
+ return unless method
163
+ method = method.upcase
164
+ if HTTP_METHODS.include?(method)
165
+ env["REQUEST_METHOD"] = method
166
+ end
167
+ end
168
+
169
+ def sanitize_query_string(env)
170
+ query_hash = env["rack.request.query_hash"]
171
+ query_hash.delete("_method")
172
+ query_hash.delete_if do |key, value|
173
+ key =~ /^openid\./
174
+ end
175
+
176
+ env["QUERY_STRING"] = env["rack.request.query_string"] =
177
+ Rack::Utils.build_query(env["rack.request.query_hash"])
178
+
179
+ qs = env["QUERY_STRING"]
180
+ request_uri = (env["PATH_INFO"] || "").dup
181
+ request_uri << "?" + qs unless qs == ""
182
+ env["REQUEST_URI"] = request_uri
183
+ end
184
+
185
+ def realm_url(req)
186
+ url = req.scheme + "://"
187
+ url << req.host
188
+
189
+ scheme, port = req.scheme, req.port
190
+ if scheme == "https" && port != 443 ||
191
+ scheme == "http" && port != 80
192
+ url << ":#{port}"
193
+ end
194
+
195
+ url
196
+ end
197
+
198
+ def request_url(req)
199
+ url = realm_url(req)
200
+ url << req.script_name
201
+ url << req.path_info
202
+ url
203
+ end
204
+
205
+ def redirect_to(url)
206
+ [303, {"Content-Type" => "text/html", "Location" => url}, []]
207
+ end
208
+
209
+ def open_id_redirect_url(req, oidreq, trust_root = nil, return_to = nil, method = nil)
210
+ request_url = request_url(req)
211
+
212
+ if return_to
213
+ method ||= "get"
214
+ else
215
+ return_to = request_url
216
+ method ||= req.request_method
217
+ end
218
+
219
+ method = method.to_s.downcase
220
+ oidreq.return_to_args['_method'] = method unless method == "get"
221
+ oidreq.redirect_url(trust_root || realm_url(req), return_to || request_url)
222
+ end
223
+
224
+ def add_simple_registration_fields(oidreq, fields)
225
+ sregreq = ::OpenID::SReg::Request.new
226
+
227
+ required = Array(fields['required']).reject(&URL_FIELD_SELECTOR)
228
+ sregreq.request_fields(required, true) if required.any?
229
+
230
+ optional = Array(fields['optional']).reject(&URL_FIELD_SELECTOR)
231
+ sregreq.request_fields(optional, false) if optional.any?
232
+
233
+ policy_url = fields['policy_url']
234
+ sregreq.policy_url = policy_url if policy_url
235
+
236
+ oidreq.add_extension(sregreq)
237
+ end
238
+
239
+ def add_attribute_exchange_fields(oidreq, fields)
240
+ axreq = ::OpenID::AX::FetchRequest.new
241
+
242
+ required = Array(fields['required']).select(&URL_FIELD_SELECTOR)
243
+ optional = Array(fields['optional']).select(&URL_FIELD_SELECTOR)
244
+
245
+ if required.any? || optional.any?
246
+ required.each do |field|
247
+ axreq.add(::OpenID::AX::AttrInfo.new(field, nil, true))
248
+ end
249
+
250
+ optional.each do |field|
251
+ axreq.add(::OpenID::AX::AttrInfo.new(field, nil, false))
252
+ end
253
+
254
+ oidreq.add_extension(axreq)
255
+ end
256
+ end
257
+
258
+ def add_oauth_fields(oidreq, fields)
259
+ if fields['oauth[consumer]']
260
+ oauthreq = ::OpenID::OAuth::Request.new(fields['oauth[consumer]'], Array(fields['oauth[scope]']).join(' '))
261
+ oidreq.add_extension(oauthreq)
262
+ end
263
+ end
264
+
265
+ def add_ui_fields(oidreq, fields)
266
+ if fields['ui[mode]'] || fields['ui[icon]'] || fields['ui[lang]']
267
+ uireq = ::OpenID::UI::Request.new(fields['ui[mode]'], fields['ui[icon]'], fields['ui[lang]'])
268
+ oidreq.add_extension(uireq)
269
+ end
270
+ end
271
+
272
+ def default_store
273
+ require 'openid/store/memory'
274
+ ::OpenID::Store::Memory.new
275
+ end
276
+
277
+ def timeout_protection_from_identity_server
278
+ yield
279
+ rescue Timeout::Error
280
+ TimeoutResponse.new
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,77 @@
1
+ require 'rack/openid'
2
+ require 'rack/request'
3
+
4
+ module Rack #:nodoc:
5
+ class OpenID
6
+ # A simple OpenID middleware that restricts access to
7
+ # a single identifier.
8
+ #
9
+ # use Rack::OpenID::SimpleAuth, "http://example.org"
10
+ #
11
+ # SimpleAuth will automatically insert the required Rack::OpenID
12
+ # middleware, so <tt>use Rack::OpenID</tt> is unnecessary.
13
+ class SimpleAuth
14
+ def self.new(*args)
15
+ Rack::OpenID.new(super)
16
+ end
17
+
18
+ attr_reader :app, :identifier
19
+
20
+ def initialize(app, identifier)
21
+ @app = app
22
+ @identifier = identifier
23
+ end
24
+
25
+ def call(env)
26
+ if session_authenticated?(env)
27
+ app.call(env)
28
+ elsif successful_response?(env)
29
+ authenticate_session(env)
30
+ redirect_to requested_url(env)
31
+ else
32
+ authentication_request
33
+ end
34
+ end
35
+
36
+ private
37
+ def session(env)
38
+ env['rack.session'] || raise_session_error
39
+ end
40
+
41
+ def raise_session_error
42
+ raise RuntimeError, 'Rack::OpenID::SimpleAuth requires a session'
43
+ end
44
+
45
+ def session_authenticated?(env)
46
+ session(env)['authenticated'] == true
47
+ end
48
+
49
+ def authenticate_session(env)
50
+ session(env)['authenticated'] = true
51
+ end
52
+
53
+ def successful_response?(env)
54
+ if resp = env[OpenID::RESPONSE]
55
+ resp.status == :success && resp.display_identifier == identifier
56
+ end
57
+ end
58
+
59
+ def requested_url(env)
60
+ req = Rack::Request.new(env)
61
+ req.url
62
+ end
63
+
64
+ def redirect_to(url)
65
+ [303, {'Content-Type' => 'text/html', 'Location' => url}, []]
66
+ end
67
+
68
+ def authentication_request
69
+ [401, { OpenID::AUTHENTICATE_HEADER => www_authenticate_header }, []]
70
+ end
71
+
72
+ def www_authenticate_header
73
+ OpenID.build_header(:identifier => identifier)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,54 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{nov-rack-openid}
8
+ s.version = "1.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["nov matake"]
12
+ s.date = %q{2011-01-18}
13
+ s.description = %q{Provides a more HTTPish API around the ruby-openid library}
14
+ s.email = %q{nov@matake.jp}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "deps.rip",
25
+ "dev-deps.rip",
26
+ "lib/rack/openid.rb",
27
+ "lib/rack/openid/simple_auth.rb",
28
+ "nov-rack-openid.gemspec",
29
+ "test/test_openid.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/nov/rack-openid}
32
+ s.require_paths = ["lib"]
33
+ s.rubygems_version = %q{1.4.1}
34
+ s.summary = %q{Provides a more HTTPish API around the ruby-openid library}
35
+ s.test_files = [
36
+ "test/test_openid.rb"
37
+ ]
38
+
39
+ if s.respond_to? :specification_version then
40
+ s.specification_version = 3
41
+
42
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
+ s.add_runtime_dependency(%q<rack>, [">= 1.1"])
44
+ s.add_runtime_dependency(%q<nov-ruby-openid>, [">= 2.1.9"])
45
+ else
46
+ s.add_dependency(%q<rack>, [">= 1.1"])
47
+ s.add_dependency(%q<nov-ruby-openid>, [">= 2.1.9"])
48
+ end
49
+ else
50
+ s.add_dependency(%q<rack>, [">= 1.1"])
51
+ s.add_dependency(%q<nov-ruby-openid>, [">= 2.1.9"])
52
+ end
53
+ end
54
+
@@ -0,0 +1,393 @@
1
+ require 'test/unit'
2
+ require 'net/http'
3
+
4
+ require 'rack'
5
+ require 'rack/openid'
6
+ require 'rack/openid/simple_auth'
7
+
8
+ log = Logger.new(STDOUT)
9
+ log.level = Logger::WARN
10
+ OpenID::Util.logger = log
11
+
12
+ class MockFetcher
13
+ def initialize(app)
14
+ @app = app
15
+ end
16
+
17
+ def fetch(url, body = nil, headers = nil, limit = nil)
18
+ opts = (headers || {}).dup
19
+ opts[:input] = body
20
+ opts[:method] = "POST" if body
21
+ env = Rack::MockRequest.env_for(url, opts)
22
+
23
+ status, headers, body = @app.call(env)
24
+
25
+ buf = []
26
+ buf << "HTTP/1.1 #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]}"
27
+ headers.each { |header, value| buf << "#{header}: #{value}" }
28
+ buf << ""
29
+ body.each { |part| buf << part }
30
+
31
+ io = Net::BufferedIO.new(StringIO.new(buf.join("\n")))
32
+ res = Net::HTTPResponse.read_new(io)
33
+ res.reading_body(io, true) {}
34
+ OpenID::HTTPResponse._from_net_response(res, url)
35
+ end
36
+ end
37
+
38
+ RotsServerUrl = 'http://localhost:9292'
39
+
40
+ RotsApp = Rack::Builder.new do
41
+ require 'rots'
42
+
43
+ config = {
44
+ 'identity' => 'john.doe',
45
+ 'sreg' => {
46
+ 'nickname' => 'jdoe',
47
+ 'fullname' => 'John Doe',
48
+ 'email' => 'jhon@doe.com',
49
+ 'dob' => Date.parse('1985-09-21'),
50
+ 'gender' => 'M'
51
+ }
52
+ }
53
+
54
+ map("/%s" % config['identity']) do
55
+ run Rots::IdentityPageApp.new(config, {})
56
+ end
57
+
58
+ map '/server' do
59
+ run Rots::ServerApp.new(config, :storage => Dir.tmpdir)
60
+ end
61
+ end
62
+
63
+ OpenID.fetcher = MockFetcher.new(RotsApp)
64
+
65
+
66
+ class TestHeader < Test::Unit::TestCase
67
+ def test_build_header
68
+ assert_equal 'OpenID identity="http://example.com/"',
69
+ Rack::OpenID.build_header(:identity => "http://example.com/")
70
+ assert_equal 'OpenID identity="http://example.com/?foo=bar"',
71
+ Rack::OpenID.build_header(:identity => "http://example.com/?foo=bar")
72
+
73
+ header = Rack::OpenID.build_header(:identity => "http://example.com/", :return_to => "http://example.org/")
74
+ assert_match(/OpenID /, header)
75
+ assert_match(/identity="http:\/\/example\.com\/"/, header)
76
+ assert_match(/return_to="http:\/\/example\.org\/"/, header)
77
+
78
+ header = Rack::OpenID.build_header(:identity => "http://example.com/", :required => ["nickname", "email"])
79
+ assert_match(/OpenID /, header)
80
+ assert_match(/identity="http:\/\/example\.com\/"/, header)
81
+ assert_match(/required="nickname,email"/, header)
82
+ end
83
+
84
+ def test_parse_header
85
+ assert_equal({"identity" => "http://example.com/"},
86
+ Rack::OpenID.parse_header('OpenID identity="http://example.com/"'))
87
+ assert_equal({"identity" => "http://example.com/?foo=bar"},
88
+ Rack::OpenID.parse_header('OpenID identity="http://example.com/?foo=bar"'))
89
+ assert_equal({"identity" => "http://example.com/", "return_to" => "http://example.org/"},
90
+ Rack::OpenID.parse_header('OpenID identity="http://example.com/", return_to="http://example.org/"'))
91
+ assert_equal({"identity" => "http://example.com/", "required" => ["nickname", "email"]},
92
+ Rack::OpenID.parse_header('OpenID identity="http://example.com/", required="nickname,email"'))
93
+
94
+ # ensure we don't break standard HTTP basic auth
95
+ assert_equal({},
96
+ Rack::OpenID.parse_header('Realm="Example"'))
97
+ end
98
+ end
99
+
100
+ module RackTestHelpers
101
+ private
102
+ def process(*args)
103
+ env = Rack::MockRequest.env_for(*args)
104
+ @response = Rack::MockResponse.new(*@app.call(env))
105
+ end
106
+
107
+ def follow_redirect!
108
+ assert @response
109
+ assert_equal 303, @response.status
110
+
111
+ env = Rack::MockRequest.env_for(@response.headers['Location'])
112
+ status, headers, body = RotsApp.call(env)
113
+
114
+ uri = URI(headers['Location'])
115
+ process("#{uri.path}?#{uri.query}")
116
+ end
117
+ end
118
+
119
+ class TestOpenID < Test::Unit::TestCase
120
+ include RackTestHelpers
121
+
122
+ def test_with_get
123
+ @app = app
124
+ process('/', :method => 'GET')
125
+ follow_redirect!
126
+ assert_equal 200, @response.status
127
+ assert_equal 'GET', @response.headers['X-Method']
128
+ assert_equal '/', @response.headers['X-Path']
129
+ assert_equal 'success', @response.body
130
+ end
131
+
132
+ def test_with_deprecated_identity
133
+ @app = app
134
+ process('/', :method => 'GET', :identity => "#{RotsServerUrl}/john.doe?openid.success=true")
135
+ follow_redirect!
136
+ assert_equal 200, @response.status
137
+ assert_equal 'GET', @response.headers['X-Method']
138
+ assert_equal '/', @response.headers['X-Path']
139
+ assert_equal 'success', @response.body
140
+ end
141
+
142
+ def test_with_post_method
143
+ @app = app
144
+ process('/', :method => 'POST')
145
+ follow_redirect!
146
+ assert_equal 200, @response.status
147
+ assert_equal 'POST', @response.headers['X-Method']
148
+ assert_equal '/', @response.headers['X-Path']
149
+ assert_equal 'success', @response.body
150
+ end
151
+
152
+ def test_with_custom_return_to
153
+ @app = app(:return_to => 'http://example.org/complete')
154
+ process('/', :method => 'GET')
155
+ follow_redirect!
156
+ assert_equal 200, @response.status
157
+ assert_equal 'GET', @response.headers['X-Method']
158
+ assert_equal '/complete', @response.headers['X-Path']
159
+ assert_equal 'success', @response.body
160
+ end
161
+
162
+ def test_with_get_nested_params_custom_return_to
163
+ url = 'http://example.org/complete?user[remember_me]=true'
164
+ @app = app(:return_to => url)
165
+ process('/', :method => 'GET')
166
+ follow_redirect!
167
+ assert_equal 200, @response.status
168
+ assert_equal 'GET', @response.headers['X-Method']
169
+ assert_equal '/complete', @response.headers['X-Path']
170
+ assert_equal 'success', @response.body
171
+ assert_match(/remember_me/, @response.headers['X-Query-String'])
172
+ end
173
+
174
+ def test_with_post_nested_params_custom_return_to
175
+ url = 'http://example.org/complete?user[remember_me]=true'
176
+ @app = app(:return_to => url)
177
+ process('/', :method => 'POST')
178
+
179
+ assert_equal 303, @response.status
180
+ env = Rack::MockRequest.env_for(@response.headers['Location'])
181
+ status, headers, body = RotsApp.call(env)
182
+
183
+ uri, input = headers['Location'].split('?', 2)
184
+ process("http://example.org/complete?user[remember_me]=true", :method => 'POST', :input => input)
185
+
186
+ assert_equal 200, @response.status
187
+ assert_equal 'POST', @response.headers['X-Method']
188
+ assert_equal '/complete', @response.headers['X-Path']
189
+ assert_equal 'success', @response.body
190
+ assert_match(/remember_me/, @response.headers['X-Query-String'])
191
+ end
192
+
193
+ def test_with_post_method_custom_return_to
194
+ @app = app(:return_to => 'http://example.org/complete')
195
+ process('/', :method => 'POST')
196
+ follow_redirect!
197
+ assert_equal 200, @response.status
198
+ assert_equal 'GET', @response.headers['X-Method']
199
+ assert_equal '/complete', @response.headers['X-Path']
200
+ assert_equal 'success', @response.body
201
+ end
202
+
203
+ def test_with_custom_return_method
204
+ @app = app(:method => 'put')
205
+ process('/', :method => 'GET')
206
+ follow_redirect!
207
+ assert_equal 200, @response.status
208
+ assert_equal 'PUT', @response.headers['X-Method']
209
+ assert_equal '/', @response.headers['X-Path']
210
+ assert_equal 'success', @response.body
211
+ end
212
+
213
+ def test_with_simple_registration_fields
214
+ @app = app(:required => ['nickname', 'email'], :optional => 'fullname')
215
+ process('/', :method => 'GET')
216
+ follow_redirect!
217
+ assert_equal 200, @response.status
218
+ assert_equal 'GET', @response.headers['X-Method']
219
+ assert_equal '/', @response.headers['X-Path']
220
+ assert_equal 'success', @response.body
221
+ end
222
+
223
+ def test_with_attribute_exchange
224
+ @app = app(
225
+ :required => ['http://axschema.org/namePerson/friendly', 'http://axschema.org/contact/email'],
226
+ :optional => 'http://axschema.org/namePerson')
227
+ process('/', :method => 'GET')
228
+ follow_redirect!
229
+ assert_equal 200, @response.status
230
+ assert_equal 'GET', @response.headers['X-Method']
231
+ assert_equal '/', @response.headers['X-Path']
232
+ assert_equal 'success', @response.body
233
+ end
234
+
235
+ def test_with_oauth
236
+ @app = app(
237
+ :'oauth[consumer]' => 'www.example.com',
238
+ :'oauth[scope]' => ['http://docs.google.com/feeds/', 'http://spreadsheets.google.com/feeds/']
239
+ )
240
+ process('/', :method => 'GET')
241
+
242
+ location = @response.headers['Location']
243
+ assert_match(/openid.oauth.consumer/, location)
244
+ assert_match(/openid.oauth.scope/, location)
245
+
246
+ follow_redirect!
247
+ assert_equal 200, @response.status
248
+ assert_equal 'GET', @response.headers['X-Method']
249
+ assert_equal '/', @response.headers['X-Path']
250
+ assert_equal 'success', @response.body
251
+ end
252
+
253
+ def test_with_ui_mode
254
+ @app = app(
255
+ :'ui[mode]' => 'popup'
256
+ )
257
+ process('/', :method => 'GET')
258
+
259
+ location = @response.headers['Location']
260
+ assert_match(/openid.ui.mode/, location)
261
+
262
+ follow_redirect!
263
+ assert_equal 200, @response.status
264
+ assert_equal 'GET', @response.headers['X-Method']
265
+ assert_equal '/', @response.headers['X-Path']
266
+ assert_equal 'success', @response.body
267
+ end
268
+
269
+ def test_with_ui_icon
270
+ @app = app(
271
+ :'ui[icon]' => true
272
+ )
273
+ process('/', :method => 'GET')
274
+
275
+ location = @response.headers['Location']
276
+ assert_match(/openid.ui.icon/, location)
277
+
278
+ follow_redirect!
279
+ assert_equal 200, @response.status
280
+ assert_equal 'GET', @response.headers['X-Method']
281
+ assert_equal '/', @response.headers['X-Path']
282
+ assert_equal 'success', @response.body
283
+ end
284
+
285
+ def test_with_ui_lang
286
+ @app = app(
287
+ :'ui[lang]' => 'ja-JP'
288
+ )
289
+ process('/', :method => 'GET')
290
+
291
+ location = @response.headers['Location']
292
+ assert_match(/openid.ui.lang/, location)
293
+
294
+ follow_redirect!
295
+ assert_equal 200, @response.status
296
+ assert_equal 'GET', @response.headers['X-Method']
297
+ assert_equal '/', @response.headers['X-Path']
298
+ assert_equal 'success', @response.body
299
+ end
300
+
301
+ def test_with_missing_id
302
+ @app = app(:identifier => "#{RotsServerUrl}/john.doe")
303
+ process('/', :method => 'GET')
304
+ follow_redirect!
305
+ assert_equal 400, @response.status
306
+ assert_equal 'GET', @response.headers['X-Method']
307
+ assert_equal '/', @response.headers['X-Path']
308
+ assert_equal 'cancel', @response.body
309
+ end
310
+
311
+ def test_with_timeout
312
+ @app = app(:identifier => RotsServerUrl)
313
+ process('/', :method => "GET")
314
+ assert_equal 400, @response.status
315
+ assert_equal 'GET', @response.headers['X-Method']
316
+ assert_equal '/', @response.headers['X-Path']
317
+ assert_equal 'missing', @response.body
318
+ end
319
+
320
+ def test_sanitize_query_string
321
+ @app = app
322
+ process('/', :method => 'GET')
323
+ follow_redirect!
324
+ assert_equal 200, @response.status
325
+ assert_equal '/', @response.headers['X-Path']
326
+ assert_equal '', @response.headers['X-Query-String']
327
+ end
328
+
329
+ def test_passthrough_standard_http_basic_auth
330
+ @app = app
331
+ process('/', :method => 'GET', "MOCK_HTTP_BASIC_AUTH" => '1')
332
+ assert_equal 401, @response.status
333
+ end
334
+
335
+ private
336
+ def app(options = {})
337
+ options[:identifier] ||= "#{RotsServerUrl}/john.doe?openid.success=true"
338
+
339
+ app = lambda { |env|
340
+ if resp = env[Rack::OpenID::RESPONSE]
341
+ headers = {
342
+ 'X-Path' => env['PATH_INFO'],
343
+ 'X-Method' => env['REQUEST_METHOD'],
344
+ 'X-Query-String' => env['QUERY_STRING']
345
+ }
346
+ if resp.status == :success
347
+ [200, headers, [resp.status.to_s]]
348
+ else
349
+ [400, headers, [resp.status.to_s]]
350
+ end
351
+ elsif env["MOCK_HTTP_BASIC_AUTH"]
352
+ [401, {Rack::OpenID::AUTHENTICATE_HEADER => 'Realm="Example"'}, []]
353
+ else
354
+ [401, {Rack::OpenID::AUTHENTICATE_HEADER => Rack::OpenID.build_header(options)}, []]
355
+ end
356
+ }
357
+ Rack::Session::Pool.new(Rack::OpenID.new(app))
358
+ end
359
+ end
360
+
361
+ class TestSimpleAuth < Test::Unit::TestCase
362
+ include RackTestHelpers
363
+
364
+ def test_successful_login
365
+ @app = app "#{RotsServerUrl}/john.doe?openid.success=true"
366
+
367
+ process '/dashboard'
368
+ follow_redirect!
369
+
370
+ assert_equal 303, @response.status
371
+ assert_equal 'http://example.org/dashboard', @response.headers['Location']
372
+
373
+ cookie = @response.headers['Set-Cookie'].split(';').first
374
+ process '/dashboard', 'HTTP_COOKIE' => cookie
375
+ assert_equal 200, @response.status
376
+ assert_equal 'Hello', @response.body
377
+ end
378
+
379
+ def test_failed_login
380
+ @app = app "#{RotsServerUrl}/john.doe"
381
+
382
+ process '/dashboard'
383
+ follow_redirect!
384
+ assert_match RotsServerUrl, @response.headers['Location']
385
+ end
386
+
387
+ private
388
+ def app(identifier)
389
+ app = lambda { |env| [200, {'Content-Type' => 'text/html'}, ['Hello']] }
390
+ app = Rack::OpenID::SimpleAuth.new(app, identifier)
391
+ Rack::Session::Pool.new(app)
392
+ end
393
+ end
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: nov-rack-openid
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 2
9
+ - 1
10
+ version: 1.2.1
11
+ platform: ruby
12
+ authors:
13
+ - nov matake
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-18 00:00:00 +09:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rack
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 13
30
+ segments:
31
+ - 1
32
+ - 1
33
+ version: "1.1"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: nov-ruby-openid
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 25
45
+ segments:
46
+ - 2
47
+ - 1
48
+ - 9
49
+ version: 2.1.9
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ description: Provides a more HTTPish API around the ruby-openid library
53
+ email: nov@matake.jp
54
+ executables: []
55
+
56
+ extensions: []
57
+
58
+ extra_rdoc_files:
59
+ - LICENSE
60
+ - README.rdoc
61
+ files:
62
+ - LICENSE
63
+ - README.rdoc
64
+ - Rakefile
65
+ - VERSION
66
+ - deps.rip
67
+ - dev-deps.rip
68
+ - lib/rack/openid.rb
69
+ - lib/rack/openid/simple_auth.rb
70
+ - nov-rack-openid.gemspec
71
+ - test/test_openid.rb
72
+ has_rdoc: true
73
+ homepage: http://github.com/nov/rack-openid
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ hash: 3
87
+ segments:
88
+ - 0
89
+ version: "0"
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ hash: 3
96
+ segments:
97
+ - 0
98
+ version: "0"
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.4.1
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Provides a more HTTPish API around the ruby-openid library
106
+ test_files:
107
+ - test/test_openid.rb