rack-openid 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +24 -0
  3. data/lib/rack/openid.rb +195 -0
  4. metadata +67 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 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.
data/README.rdoc ADDED
@@ -0,0 +1,24 @@
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 completition, the OpenID response is automatically verified and assigned to env["rack.openid.response"].
10
+
11
+ MyApp = lambda { |env|
12
+ if resp = env["rack.openid.response"]
13
+ case resp.status
14
+ when :success
15
+ ...
16
+ when :failure
17
+ ...
18
+ else
19
+ [401, {"WWW-Authenticate" => 'OpenID identity="http://example.com/"'}, []]
20
+ end
21
+ }
22
+
23
+ use Rack::OpenID
24
+ run MyApp
@@ -0,0 +1,195 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+
4
+ gem 'ruby-openid', '>=2.1.6'
5
+ require 'openid'
6
+ require 'openid/consumer'
7
+ require 'openid/extensions/sreg'
8
+ require 'openid/store/memory'
9
+
10
+ module Rack
11
+ class OpenID
12
+ def self.build_header(params = {})
13
+ value = 'OpenID '
14
+ value += params.map { |k, v|
15
+ if v.is_a?(Array)
16
+ "#{k}=\"#{v.join(',')}\""
17
+ else
18
+ "#{k}=\"#{v}\""
19
+ end
20
+ }.join(', ')
21
+ value
22
+ end
23
+
24
+ def self.parse_header(str)
25
+ params = {}
26
+ if str =~ /^OpenID/
27
+ str = str.gsub(/^OpenID /, '')
28
+ str.split(', ').each { |e|
29
+ k, v = e.split('=')
30
+ v.gsub!(/^\"/, '').gsub!(/\"$/, "")
31
+ v = v.split(',')
32
+ params[k] = v.length > 1 ? v : v.first
33
+ }
34
+ end
35
+ params
36
+ end
37
+
38
+ class TimeoutResponse
39
+ include ::OpenID::Consumer::Response
40
+ STATUS = :failure
41
+ end
42
+
43
+ class MissingResponse
44
+ include ::OpenID::Consumer::Response
45
+ STATUS = :missing
46
+ end
47
+
48
+ HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS)
49
+
50
+ RESPONSE = "rack.openid.response".freeze
51
+ AUTHENTICATE_HEADER = "WWW-Authenticate".freeze
52
+
53
+
54
+ def initialize(app, store = nil)
55
+ @app = app
56
+ @store = store || ::OpenID::Store::Memory.new
57
+ freeze
58
+ end
59
+
60
+ def call(env)
61
+ req = Rack::Request.new(env)
62
+ if env["REQUEST_METHOD"] == "GET" && req.GET["openid.mode"]
63
+ complete_authentication(env)
64
+ end
65
+
66
+ status, headers, body = @app.call(env)
67
+
68
+ if status.to_i == 401 && (qs = headers[AUTHENTICATE_HEADER])
69
+ begin_authentication(env, qs)
70
+ else
71
+ [status, headers, body]
72
+ end
73
+ end
74
+
75
+ private
76
+ def begin_authentication(env, qs)
77
+ req = Rack::Request.new(env)
78
+ params = self.class.parse_header(qs)
79
+
80
+ unless session = env["rack.session"]
81
+ raise RuntimeError, "Rack::OpenID requires a session"
82
+ end
83
+
84
+ consumer = ::OpenID::Consumer.new(session, @store)
85
+ identifier = params["identifier"]
86
+
87
+ begin
88
+ oidreq = consumer.begin(identifier)
89
+ add_simple_registration_fields(oidreq, params)
90
+ url = open_id_redirect_url(req, oidreq, params["trust_root"], params["return_to"], params["method"])
91
+ return redirect_to(url)
92
+ rescue ::OpenID::OpenIDError, Timeout::Error => e
93
+ env[RESPONSE] = MissingResponse.new
94
+ return @app.call(env)
95
+ end
96
+ end
97
+
98
+ def complete_authentication(env)
99
+ req = Rack::Request.new(env)
100
+
101
+ unless session = env["rack.session"]
102
+ raise RuntimeError, "Rack::OpenID requires a session"
103
+ end
104
+
105
+ oidresp = timeout_protection_from_identity_server {
106
+ consumer = ::OpenID::Consumer.new(session, @store)
107
+ consumer.complete(req.params, req.url)
108
+ }
109
+
110
+ env[RESPONSE] = oidresp
111
+
112
+ if method = req.GET["_method"]
113
+ method = method.upcase
114
+ if HTTP_METHODS.include?(method)
115
+ env["REQUEST_METHOD"] = method
116
+ end
117
+ end
118
+
119
+ query_hash = env["rack.request.query_hash"]
120
+ query_hash.delete("_method")
121
+ query_hash.delete_if do |key, value|
122
+ key =~ /^openid\./
123
+ end
124
+
125
+ env["QUERY_STRING"] = env["rack.request.query_string"] =
126
+ Rack::Utils.build_query(env["rack.request.query_hash"])
127
+
128
+ request_uri = env["PATH_INFO"]
129
+ if env["QUERY_STRING"].any?
130
+ request_uri << "?" + env["QUERY_STRING"]
131
+ end
132
+ env["REQUEST_URI"] = request_uri
133
+ end
134
+
135
+ def realm_url(req)
136
+ url = req.scheme + "://"
137
+ url << req.host
138
+
139
+ if req.scheme == "https" && req.port != 443 ||
140
+ req.scheme == "http" && req.port != 80
141
+ url << ":#{req.port}"
142
+ end
143
+
144
+ url
145
+ end
146
+
147
+ def request_url(req)
148
+ url = realm_url(req)
149
+ url << req.script_name
150
+ url << req.path_info
151
+ url
152
+ end
153
+
154
+ def redirect_to(url)
155
+ [303, {"Content-Type" => "text/html", "Location" => url}, []]
156
+ end
157
+
158
+ def open_id_redirect_url(req, oidreq, trust_root = nil, return_to = nil, method = nil)
159
+ if return_to
160
+ method ||= "get"
161
+ else
162
+ return_to = request_url(req)
163
+ method ||= req.request_method
164
+ end
165
+
166
+ method = method.to_s.downcase
167
+ oidreq.return_to_args['_method'] = method unless method == "get"
168
+ oidreq.redirect_url(trust_root || realm_url(req), return_to || request_url(req))
169
+ end
170
+
171
+ def add_simple_registration_fields(oidreq, fields)
172
+ sregreq = ::OpenID::SReg::Request.new
173
+
174
+ if required = fields["required"]
175
+ sregreq.request_fields(Array(required), true)
176
+ end
177
+
178
+ if optional = fields["optional"]
179
+ sregreq.request_fields(Array(optional), false)
180
+ end
181
+
182
+ if policy_url = fields["policy_url"]
183
+ sregreq.policy_url = policy_url
184
+ end
185
+
186
+ oidreq.add_extension(sregreq)
187
+ end
188
+
189
+ def timeout_protection_from_identity_server
190
+ yield
191
+ rescue Timeout::Error
192
+ TimeoutResponse.new
193
+ end
194
+ end
195
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-openid
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joshua Peek
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-05-04 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rack
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 1.0.0
24
+ version:
25
+ description: Provides a more HTTPish API around the ruby-openid library
26
+ email: josh@joshpeek.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README.rdoc
33
+ - MIT-LICENSE
34
+ files:
35
+ - lib/rack/openid.rb
36
+ - README.rdoc
37
+ - MIT-LICENSE
38
+ has_rdoc: true
39
+ homepage: http://github.com/josh/rack-openid
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project: rack-openid
62
+ rubygems_version: 1.3.2
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Provides a more HTTPish API around the ruby-openid library
66
+ test_files: []
67
+