/
open_id_authentication.rb
170 lines (141 loc) · 4.91 KB
/
open_id_authentication.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
require 'uri'
require 'openid/extensions/sreg'
require 'openid/store/filesystem'
module OpenIdAuthentication
OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids"
def self.store
@@store
end
def self.store=(value)
@@store = value
end
self.store = :db
def store
OpenIdAuthentication.store
end
class InvalidOpenId < StandardError
end
class Result
ERROR_MESSAGES = {
:missing => "Sorry, the OpenID server couldn't be found",
:canceled => "OpenID verification was canceled",
:failed => "OpenID verification failed",
:setup_needed => "OpenID verification needs setup"
}
def self.[](code)
new(code)
end
def initialize(code)
@code = code
end
def ===(code)
if code == :unsuccessful && unsuccessful?
true
else
@code == code
end
end
ERROR_MESSAGES.keys.each { |state| define_method("#{state}?") { @code == state } }
def successful?
@code == :successful
end
def unsuccessful?
ERROR_MESSAGES.keys.include?(@code)
end
def message
ERROR_MESSAGES[@code]
end
end
def self.normalize_url(url)
uri = URI.parse(url.to_s.strip)
uri = URI.parse("http://#{uri}") unless uri.scheme
uri.scheme = uri.scheme.downcase # URI should do this
uri.normalize.to_s
rescue URI::InvalidURIError
raise InvalidOpenId.new("#{url} is not an OpenID URL")
end
protected
def normalize_url(url)
OpenIdAuthentication.normalize_url(url)
end
# The parameter name of "openid_url" is used rather than the Rails convention "open_id_url"
# because that's what the specification dictates in order to get browser auto-complete working across sites
def using_open_id?(identity_url = params[:openid_url]) #:doc:
!identity_url.blank? || params[:open_id_complete]
end
def authenticate_with_open_id(identity_url = params[:openid_url], options = {}, &block) #:doc:
if params[:open_id_complete].nil?
begin_open_id_authentication(normalize_url(identity_url), options, &block)
else
complete_open_id_authentication(&block)
end
end
private
def begin_open_id_authentication(identity_url, options = {})
return_to = options.delete(:return_to)
open_id_request = open_id_consumer.begin(identity_url)
add_simple_registration_fields(open_id_request, options)
redirect_to(open_id_redirect_url(open_id_request, return_to))
rescue OpenID::OpenIDError, Timeout::Error => e
logger.error("[OPENID] #{e}")
yield Result[:missing], identity_url, nil
end
def complete_open_id_authentication
params_with_path = params.reject { |key, value| request.path_parameters[key] }
params_with_path.delete(:format)
open_id_response = timeout_protection_from_identity_server { open_id_consumer.complete(params_with_path, requested_url) }
identity_url = normalize_url(open_id_response.endpoint.claimed_id) if open_id_response.endpoint.claimed_id
case open_id_response.status
when OpenID::Consumer::SUCCESS
yield Result[:successful], identity_url, OpenID::SReg::Response.from_success_response(open_id_response)
when OpenID::Consumer::CANCEL
yield Result[:canceled], identity_url, nil
when OpenID::Consumer::FAILURE
yield Result[:failed], identity_url, nil
when OpenID::Consumer::SETUP_NEEDED
yield Result[:setup_needed], open_id_response.setup_url, nil
end
end
def open_id_consumer
OpenID::Consumer.new(session, open_id_store)
end
def open_id_store
case store
when :db
OpenIdAuthentication::DbStore.new
when :file
OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR)
else
raise "Unknown store: #{store}"
end
end
def add_simple_registration_fields(open_id_request, fields)
sreg_request = OpenID::SReg::Request.new
sreg_request.request_fields(Array(fields[:required]).map(&:to_s), true) if fields[:required]
sreg_request.request_fields(Array(fields[:optional]).map(&:to_s), false) if fields[:optional]
sreg_request.policy_url = fields[:policy_url] if fields[:policy_url]
open_id_request.add_extension(sreg_request)
end
def open_id_redirect_url(open_id_request, return_to = nil)
open_id_request.return_to_args['open_id_complete'] = '1'
open_id_request.redirect_url(realm, return_to || requested_url)
end
def requested_url
"#{request.protocol + request.host_with_port + request.relative_url_root + request.path}"
end
def realm
respond_to?(:root_url) ? root_url : "#{request.protocol + request.host_with_port}"
end
def timeout_protection_from_identity_server
yield
rescue Timeout::Error
Class.new do
def status
OpenID::FAILURE
end
def msg
"Identity server timed out"
end
end.new
end
end