Class: Rack::Auth::OpenID
- Defined in:
- lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb
Overview
Rack::Auth::OpenID provides a simple method for setting up an OpenID Consumer. It requires the ruby-openid library from janrain to operate, as well as a rack method of session management.
The ruby-openid home page is at openidenabled.com/ruby-openid/.
The OpenID specifications can be found at openid.net/specs/openid-authentication-1_1.html and openid.net/specs/openid-authentication-2_0.html. Documentation for published OpenID extensions and related topics can be found at openid.net/developers/specs/.
It is recommended to read through the OpenID spec, as well as ruby-openid’s documentation, to understand what exactly goes on. However a setup as simple as the presented examples is enough to provide Consumer functionality.
This library strongly intends to utilize the OpenID 2.0 features of the ruby-openid library, which provides OpenID 1.0 compatiblity.
NOTE: Due to the amount of data that this library stores in the session, Rack::Session::Cookie may fault.
Defined Under Namespace
Classes: BadExtension, NoSession
Constant Summary collapse
- ValidStatus =
Required for ruby-openid
[:success, :setup_needed, :cancel, :failure]
Instance Attribute Summary collapse
-
#extensions ⇒ Object
readonly
Returns the value of attribute extensions.
-
#immediate ⇒ Object
readonly
Returns the value of attribute immediate.
-
#openid_param ⇒ Object
readonly
Returns the value of attribute openid_param.
-
#realm ⇒ Object
readonly
Returns the value of attribute realm.
-
#return_to ⇒ Object
readonly
Returns the value of attribute return_to.
-
#session_key ⇒ Object
readonly
Returns the value of attribute session_key.
-
#store ⇒ Object
readonly
Returns the value of attribute store.
Instance Method Summary collapse
-
#add_extension(ext, *args) ⇒ Object
The first argument should be the main extension module.
-
#call(env) ⇒ Object
Sets up and uses session data at
:openid
within the session. -
#check(consumer, session, req) ⇒ Object
As the first part of OpenID consumer action, #check retrieves the data required for completion.
-
#finish(consumer, session, req) ⇒ Object
This is the final portion of authentication.
-
#initialize(realm, options = {}) ⇒ OpenID
constructor
Arguments.
-
#valid_extension?(ext) ⇒ Boolean
Checks the validitity, in the context of usage, of a submitted extension.
-
#within_realm?(uri) ⇒ Boolean
(also: #include?)
Checks the provided uri to ensure it’d be considered within the realm.
Constructor Details
#initialize(realm, options = {}) ⇒ OpenID
Arguments
The first argument is the realm, identifying the site they are trusting with their identity. This is required, also treated as the trust_root in OpenID 1.x exchanges.
The optional second argument is a hash of options.
Options
:return_to
defines the url to return to after the client authenticates with the openid service provider. This url should point to where Rack::Auth::OpenID is mounted. If :return_to
is not provided, return_to will be the current url which allows flexibility with caveats.
:session_key
defines the key to the session hash in the env. It defaults to ‘rack.session’.
:openid_param
defines at what key in the request parameters to find the identifier to resolve. As per the 2.0 spec, the default is ‘openid_identifier’.
:store
defined what OpenID Store to use for persistant information. By default a Store::Memory will be used.
:immediate
as true will make initial requests to be of an immediate type. This is false by default. See OpenID specification documentation.
:extensions
should be a hash of openid extension implementations. The key should be the extension main module, the value should be an array of arguments for extension::Request.new. The hash is iterated over and passed to #add_extension for processing. Please see #add_extension for further documentation.
Examples
simple_oid = OpenID.new('http://mysite.com/')
return_oid = OpenID.new('http://mysite.com/', {
:return_to => 'http://mysite.com/openid'
})
complex_oid = OpenID.new('http://mysite.com/',
:immediate => true,
:extensions => {
::OpenID::SReg => [['email'],['nickname']]
}
)
Advanced
Most of the functionality of this library is encapsulated such that expansion and overriding functions isn’t difficult nor tricky. Alternately, to avoid opening up singleton objects or subclassing, a wrapper rack middleware can be composed to act upon Auth::OpenID’s responses. See #check and #finish for locations of pertinent data.
Responses
To change the responses that Auth::OpenID returns, override the methods #redirect, #bad_request, #unauthorized, #access_denied, and #foreign_server_failure.
Additionally #confirm_post_params is used when the URI would exceed length limits on a GET request when doing the initial verification request.
Processing
To change methods of processing completed transactions, override the methods #success, #setup_needed, #cancel, and #failure. Please ensure the returned object is a rack compatible response.
The first argument is an OpenID::Response, the second is a Rack::Request of the current request, the last is the hash used in ruby-openid handling, which can be found manually at env[:openid].
This is useful if you wanted to expand the processing done, such as setting up user accounts.
oid_app = Rack::Auth::OpenID.new realm, :return_to => return_to
def oid_app.success oid, request, session
user = Models::User[oid.identity_url]
user ||= Models::User.create_from_openid oid
request['rack.session'][:user] = user.id
redirect MyApp.site_home
end
site_map['/openid'] = oid_app
map = Rack::URLMap.new site_map
...
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 151 def initialize(realm, ={}) realm = URI(realm) raise ArgumentError, "Invalid realm: #{realm}" \ unless realm.absolute? \ and realm.fragment.nil? \ and realm.scheme =~ /^https?$/ \ and realm.host =~ /^(\*\.)?#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+/ realm.path = '/' if realm.path.empty? @realm = realm.to_s if ruri = [:return_to] ruri = URI(ruri) raise ArgumentError, "Invalid return_to: #{ruri}" \ unless ruri.absolute? \ and ruri.scheme =~ /^https?$/ \ and ruri.fragment.nil? raise ArgumentError, "return_to #{ruri} not within realm #{realm}" \ unless self.within_realm?(ruri) @return_to = ruri.to_s end @session_key = [:session_key] || 'rack.session' @openid_param = [:openid_param] || 'openid_identifier' @store = [:store] || ::OpenID::Store::Memory.new @immediate = !![:immediate] @extensions = {} if extensions = .delete(:extensions) extensions.each do |ext, args| add_extension ext, *args end end # Undocumented, semi-experimental @anonymous = !![:anonymous] end |
Instance Attribute Details
#extensions ⇒ Object (readonly)
Returns the value of attribute extensions.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def extensions @extensions end |
#immediate ⇒ Object (readonly)
Returns the value of attribute immediate.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def immediate @immediate end |
#openid_param ⇒ Object (readonly)
Returns the value of attribute openid_param.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def openid_param @openid_param end |
#realm ⇒ Object (readonly)
Returns the value of attribute realm.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def realm @realm end |
#return_to ⇒ Object (readonly)
Returns the value of attribute return_to.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def return_to @return_to end |
#session_key ⇒ Object (readonly)
Returns the value of attribute session_key.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def session_key @session_key end |
#store ⇒ Object (readonly)
Returns the value of attribute store.
188 189 190 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 188 def store @store end |
Instance Method Details
#add_extension(ext, *args) ⇒ Object
The first argument should be the main extension module. The extension module should contain the constants:
* class Request, should have OpenID::Extension as an ancestor
* class Response, should have OpenID::Extension as an ancestor
* string NS_URI, which defining the namespace of the extension
All trailing arguments will be passed to extension::Request.new in #check. The openid response will be passed to extension::Response#from_success_response, #get_extension_args will be called on the result to attain the gathered data.
This method returns the key at which the response data will be found in the session, which is the namespace uri by default.
312 313 314 315 316 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 312 def add_extension(ext, *args) raise BadExtension unless valid_extension?(ext) extensions[ext] = args return ext::NS_URI end |
#call(env) ⇒ Object
Sets up and uses session data at :openid
within the session. Errors in this setup will raise a NoSession exception.
If the parameter ‘openid.mode’ is set, which implies a followup from the openid server, processing is passed to #finish and the result is returned. However, if there is no appropriate openid information in the session, a 400 error is returned.
If the parameter specified by options[:openid_param]
is present, processing is passed to #check and the result is returned.
If neither of these conditions are met, #unauthorized is called.
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 204 def call(env) env['rack.auth.openid'] = self env_session = env[@session_key] unless env_session and env_session.is_a?(Hash) raise NoSession, 'No compatible session' end # let us work in our own namespace... session = (env_session[:openid] ||= {}) unless session and session.is_a?(Hash) raise NoSession, 'Incompatible openid session' end request = Rack::Request.new(env) consumer = ::OpenID::Consumer.new(session, @store) if mode = request.GET['openid.mode'] if session.key?(:openid_param) finish(consumer, session, request) else bad_request end elsif request.GET[@openid_param] check(consumer, session, request) else end end |
#check(consumer, session, req) ⇒ Object
As the first part of OpenID consumer action, #check retrieves the data required for completion.
If all parameters fit within the max length of a URI, a 303 redirect will be returned. Otherwise #confirm_post_params will be called.
Any messages from OpenID’s request are logged to env
env['rack.auth.openid.request']
is the openid checkid request instance.
session[:openid_param]
is set to the openid identifier provided by the user.
session[:return_to]
is set to the return_to uri given to the identity provider.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 249 def check(consumer, session, req) oid = consumer.begin(req.GET[@openid_param], @anonymous) req.env['rack.auth.openid.request'] = oid req.env['rack.errors'].puts(oid.) p oid if $DEBUG ## Extension support extensions.each do |ext,args| oid.add_extension(ext::Request.new(*args)) end session[:openid_param] = req.GET[openid_param] return_to_uri = return_to ? return_to : req.url session[:return_to] = return_to_uri immediate = session.key?(:setup_needed) ? false : immediate if oid.send_redirect?(realm, return_to_uri, immediate) uri = oid.redirect_url(realm, return_to_uri, immediate) redirect(uri) else confirm_post_params(oid, realm, return_to_uri, immediate) end rescue ::OpenID::DiscoveryFailure => e # thrown from inside OpenID::Consumer#begin by yadis stuff req.env['rack.errors'].puts([e., *e.backtrace]*"\n") return foreign_server_failure end |
#finish(consumer, session, req) ⇒ Object
This is the final portion of authentication. If successful, a redirect to the realm is be returned. Data gathered from extensions are stored in session with the extension’s namespace uri as the key.
Any messages from OpenID’s response are logged to env
env['rack.auth.openid.response']
will contain the openid response.
287 288 289 290 291 292 293 294 295 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 287 def finish(consumer, session, req) oid = consumer.complete(req.GET, req.url) req.env['rack.auth.openid.response'] = oid req.env['rack.errors'].puts(oid.) p oid if $DEBUG raise unless ValidStatus.include?(oid.status) __send__(oid.status, oid, req, session) end |
#valid_extension?(ext) ⇒ Boolean
Checks the validitity, in the context of usage, of a submitted extension.
321 322 323 324 325 326 327 328 329 330 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 321 def valid_extension?(ext) if not %w[NS_URI Request Response].all?{|c| ext.const_defined?(c) } raise ArgumentError, 'Extension is missing constants.' elsif not ext::Response.respond_to?(:from_success_response) raise ArgumentError, 'Response is missing required method.' end return true rescue return false end |
#within_realm?(uri) ⇒ Boolean Also known as: include?
Checks the provided uri to ensure it’d be considered within the realm. is currently not compatible with wildcard realms.
335 336 337 338 339 340 341 342 343 344 345 346 |
# File 'lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb', line 335 def within_realm? uri uri = URI.parse(uri.to_s) realm = URI.parse(self.realm) return false unless uri.absolute? return false unless uri.path[0, realm.path.size] == realm.path return false unless uri.host == realm.host or realm.host[/^\*\./] # for wildcard support, is awkward with URI limitations realm_match = Regexp.escape(realm.host). sub(/^\*\./,"^#{URI::REGEXP::PATTERN::URIC_NO_SLASH}+.")+'$' return false unless uri.host.match(realm_match) return true end |