From: Ævar Arnfjörð Bjarmason Date: Tue, 27 Apr 2010 20:03:03 +0000 (+0000) Subject: Merge branch 'master' into openID X-Git-Tag: live~6332 X-Git-Url: https://git.openstreetmap.org/rails.git/commitdiff_plain/f699368335694f663755c7f714a1ac358bc679cb?hp=feb9582b071d6eb44bcc6fbc027049dd1f5c2042 Merge branch 'master' into openID --- diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb index 9551ac6d8..f9755204a 100644 --- a/app/controllers/user_controller.rb +++ b/app/controllers/user_controller.rb @@ -24,13 +24,71 @@ class UserController < ApplicationController if Acl.find_by_address(request.remote_ip, :conditions => {:k => "no_account_creation"}) render :action => 'new' else - @user = User.new(params[:user]) - - @user.visible = true - @user.data_public = true - @user.description = "" if @user.description.nil? - @user.creation_ip = request.remote_ip - @user.languages = request.user_preferred_languages + #The redirect from the OpenID provider reenters here again + #and we need to pass the parameters through to the + #open_id_authentication function a second time + if params[:open_id_complete] + openid_verify('', true) + #We have set the user.openid_url to nil beforehand. If it hasn't + #been set to a new valid openid_url, it means the openid couldn't be validated + if @user.nil? or @user.openid_url.nil? + render :action => 'new' + return + end + else + @user = User.new(params[:user]) + + @user.visible = true + @user.data_public = true + @user.description = "" if @user.description.nil? + @user.creation_ip = request.remote_ip + @user.languages = request.user_preferred_languages + #Set the openid_url to nil as for one it is used + #to check if the openid could be validated and secondly + #to not get dupplicate conflicts for an empty openid + @user.openid_url = nil + + if (!params[:user][:openid_url].nil? and params[:user][:openid_url].length > 0) + if @user.pass_crypt.length == 0 + #if the password is empty, but we have a openid + #then generate a random passowrd to disable + #loging in via password + @user.pass_crypt = ActiveSupport::SecureRandom.base64(16) + @user.pass_crypt_confirmation = @user.pass_crypt + end + #Validate all of the other fields before + #redirecting to the openid provider + if !@user.valid? + render :action => 'new' + else + #TODO: Is it a problem to store the user variable with respect to password safty in the session variables? + #Store the user variable in the session for it to be accessible when redirecting back from the openid provider + session[:new_usr] = @user + begin + @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url]) + rescue + flash.now[:error] = t 'user.login.openid invalid' + render :action => 'new' + return + end + #Verify that the openid provided is valid and that the user is the owner of the id + openid_verify(@norm_openid_url, true) + #openid_verify can return in two ways: + #Either it returns with a redirect to the openid provider who then freshly + #redirects back to this url if the openid is valid, or if the openid is not plausible + #and no provider for it could be found it just returns + #we want to just let the redirect through + if response.headers["Location"].nil? + render :action => 'new' + end + end + #At this point there was either an error and the page has been rendered, + #or there is a redirect to the openid provider and the rest of the method + #gets executed whenn this method gets reentered after redirecting back + #from the openid provider + return + end + end if @user.save flash[:notice] = t 'user.new.flash create success message' @@ -46,6 +104,15 @@ class UserController < ApplicationController @title = t 'user.account.title' @tokens = @user.oauth_tokens.find :all, :conditions => 'oauth_tokens.invalidated_at is null and oauth_tokens.authorized_at is not null' + #The redirect from the OpenID provider reenters here again + #and we need to pass the parameters through to the + #open_id_authentication function + if params[:open_id_complete] + openid_verify('', false) + @user.save + return + end + if params[:user] and params[:user][:display_name] and params[:user][:description] @user.display_name = params[:user][:display_name] @user.new_email = params[:user][:new_email] @@ -81,6 +148,21 @@ class UserController < ApplicationController end end end + + if (params[:user][:openid_url].length > 0) + begin + @norm_openid_url = OpenIdAuthentication.normalize_identifier(params[:user][:openid_url]) + if (@norm_openid_url != @user.openid_url) + #If the OpenID has changed, we want to check that it is a valid OpenID and one + #the user has control over before saving the openID as a password equivalent for + #the user. + openid_verify(@norm_openid_url, false) + end + rescue + flash.now[:error] = t 'user.login.openid invalid' + end + end + else if flash[:errors] flash[:errors].each do |attr,msg| @@ -91,6 +173,91 @@ class UserController < ApplicationController end end + def openid_specialcase_mapping(openid_url) + #Special case gmail.com, as it is pontentially a popular OpenID provider and unlike + #yahoo.com, where it works automatically, Google have hidden their OpenID endpoint + #somewhere obscure making it less userfriendly. + if (openid_url.match(/(.*)gmail.com(\/?)$/) or openid_url.match(/(.*)googlemail.com(\/?)$/) ) + return 'https://www.google.com/accounts/o8/id' + end + + return nil + end + + def openid_verify(openid_url,account_create) + authenticate_with_open_id(openid_url) do |result, identity_url| + if result.successful? + #We need to use the openid url passed back from the OpenID provider + #rather than the one supplied by the user, as these can be different. + #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url + #only once it comes back from the OpenID provider do we know the unique address for + #the user. + @user = session[:new_usr] unless @user #this is used for account creation when the user is not yet in the database + @user.openid_url = identity_url + elsif result.missing? + mapped_id = openid_specialcase_mapping(openid_url) + if mapped_id + openid_verify(mapped_id, account_create) + else + flash.now[:error] = t 'user.login.openid missing provider' + end + elsif result.invalid? + flash.now[:error] = t 'user.login.openid invalid' + else + flash.now[:error] = t 'user.login.auth failure' + end + end + end + + def open_id_authentication(openid_url) + #TODO: only ask for nickname and email, if we don't already have a user for that openID, in which case + #email and nickname are already filled out. I don't know how to do that with ruby syntax though, as we + #don't want to duplicate the do block + #On the other hand it also doesn't matter too much if we ask every time, as the OpenID provider should + #remember these results, and shouldn't repromt the user for these data each time. + user = nil + authenticate_with_open_id(openid_url, :return_to => request.protocol + request.host_with_port + '/login?referer=' + params[:referer], :optional => [:nickname, :email]) do |result, identity_url, registration| + if result.successful? + #We need to use the openid url passed back from the OpenID provider + #rather than the one supplied by the user, as these can be different. + #e.g. one can simply enter yahoo.com in the login box, i.e. no user specific url + #only once it comes back from the OpenID provider do we know the unique address for + #the user. + user = User.find_by_openid_url(identity_url) + if user + if user.visible? and user.active? + session[:user] = user.id + session_expires_after 1.month if session[:remember] + return user + else + user = nil + flash.now[:error] = t 'user.login.account not active' + end + else + #We don't have a user registered to this OpenID. Redirect to the create account page + #with username and email filled in if they have been given by the OpenID provider through + #the simple registration protocol + redirect_to :controller => 'user', :action => 'new', :nickname => registration['nickname'], :email => registration['email'], :openid => identity_url + end + else if result.missing? + #Try and apply some heuristics to make common cases more userfriendly + mapped_id = openid_specialcase_mapping(openid_url) + if mapped_id + open_id_authentication(mapped_id) + else + flash.now[:error] = t 'user.login.openid missing provider' + end + else if result.invalid? + flash.now[:error] = t 'user.login.openid invalid' + else + flash.now[:error] = t 'user.login.auth failure' + end + end + end + end + return user + end + def go_public @user.data_public = true @user.save @@ -149,34 +316,52 @@ class UserController < ApplicationController # The user is logged in already, so don't show them the signup # page, instead send them to the home page redirect_to :controller => 'site', :action => 'index' if session[:user] + + @nickname = params['nickname'] + @email = params['email'] + @openID = params['openid'] end def login @title = t 'user.login.title' - if params[:user] - email_or_display_name = params[:user][:email] - pass = params[:user][:password] - user = User.authenticate(:username => email_or_display_name, :password => pass) - - if user - session[:user] = user.id - session_expires_after 1.month if params[:remember_me] - - # The user is logged in, if the referer param exists, redirect - # them to that unless they've also got a block on them, in - # which case redirect them to the block so they can clear it. - if user.blocked_on_view - redirect_to user.blocked_on_view, :referrer => params[:referrer] - elsif params[:referer] - redirect_to params[:referer] + #The redirect from the OpenID provider reenters here again + #and we need to pass the parameters through to the + # open_id_authentication function + if params[:open_id_complete] + user = open_id_authentication('') + elsif params[:user] + if !params[:user][:openid_url].nil? and !params[:user][:openid_url].empty? + session[:remember] = params[:remember_me] + #construct the openid request. This will redirect to the OpenID server to ask for validation + #The external OpenID server will then redirect back to the login method and reenters at the top + open_id_authentication(params[:user][:openid_url]) + return + else + email_or_display_name = params[:user][:email] + pass = params[:user][:password] + + if user = User.authenticate(:username => email_or_display_name, :password => pass) + session[:user] = user.id + session_expires_after 1.month if params[:remember_me] + elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true) + flash.now[:error] = t 'user.login.account not active' else - redirect_to :controller => 'site', :action => 'index' + flash.now[:error] = t 'user.login.auth failure' end - elsif User.authenticate(:username => email_or_display_name, :password => pass, :inactive => true) - flash.now[:error] = t 'user.login.account not active' + end + end + + if user + # The user is logged in, if the referer param exists, redirect + # them to that unless they've also got a block on them, in + # which case redirect them to the block so they can clear it. + if user.blocked_on_view + redirect_to user.blocked_on_view, :referrer => params[:referrer] + elsif params[:referer] + redirect_to params[:referer] else - flash.now[:error] = t 'user.login.auth failure' + redirect_to :controller => 'site', :action => 'index' end end end diff --git a/app/models/user.rb b/app/models/user.rb index f02c9a5cd..09e1a7d35 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -22,6 +22,7 @@ class User < ActiveRecord::Base validates_confirmation_of :pass_crypt#, :message => ' must match the confirmation password' validates_uniqueness_of :display_name, :allow_nil => true validates_uniqueness_of :email + validates_uniqueness_of :openid_url, :allow_nil => true validates_length_of :pass_crypt, :within => 8..255 validates_length_of :display_name, :within => 3..255, :allow_nil => true validates_email_format_of :email diff --git a/app/views/user/account.html.erb b/app/views/user/account.html.erb index 85e9aebef..e1fe719e0 100644 --- a/app/views/user/account.html.erb +++ b/app/views/user/account.html.erb @@ -26,6 +26,10 @@ <%= t 'user.new.confirm password' %> <%= f.password_field :pass_crypt_confirmation, {:value => '', :size => 30, :maxlength => 255, :autocomplete => :off} %> + + <%= t 'user.account.openid.openid' %> + <%= f.text_field :openid_url %> (<%= t 'user.account.openid.link text' %>) + <%= t 'user.account.public editing.heading' %> diff --git a/app/views/user/login.html.erb b/app/views/user/login.html.erb index cf7f4a819..6cac5cecb 100644 --- a/app/views/user/login.html.erb +++ b/app/views/user/login.html.erb @@ -1,14 +1,64 @@ + +

<%= t 'user.login.heading' %>

<%= t 'user.login.please login', :create_user_link => link_to(t('user.login.create_account'), :controller => 'user', :action => 'new', :referer => params[:referer]) %>

<% form_tag :action => 'login' do %> -<%= hidden_field_tag('referer', h(params[:referer])) %> - - - - - - -
<%= t 'user.login.email or username' %><%= text_field('user', 'email',{:value => "", :size => 28, :maxlength => 255, :tabindex => 1}) %>
<%= t 'user.login.password' %><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %> (<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)
<%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %>
 
<%= submit_tag t('user.login.login_button'), :tabindex => 3 %>
+ <%= hidden_field_tag('referer', h(params[:referer])) %> +
+
+

<%= t 'user.login.username_heading' %>

+ + + + +
<%= t 'user.login.email or username' %><%= text_field('user', 'email',{:value => "", :size => 28, :maxlength => 255, :tabindex => 1}) %>
<%= t 'user.login.password' %><%= password_field('user', 'password',{:value => "", :size => 28, :maxlength => 255, :tabindex => 2}) %>
(<%= link_to t('user.login.lost password link'), :controller => 'user', :action => 'lost_password' %>)
+
+
+

<%= t 'user.login.or' %>

+
+ + +
+ + +
<%= check_box_tag "remember_me", "yes", false, :tabindex => 3 %><%= submit_tag t('user.login.login_button'), :tabindex => 3 %>
+
+
<% end %> diff --git a/app/views/user/new.html.erb b/app/views/user/new.html.erb index bd85664c0..e7aa9313d 100644 --- a/app/views/user/new.html.erb +++ b/app/views/user/new.html.erb @@ -21,18 +21,21 @@ <% form_tag :action => 'save' do %> <%= hidden_field_tag('referer', h(params[:referer])) unless params[:referer].nil? %> - + - + - + + + +
<%= t 'user.new.email address' %><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1}) %>
<%= t 'user.new.email address' %><%= text_field('user', 'email',{:size => 50, :maxlength => 255, :tabindex => 1, :value => @email}) %>
<%= t 'user.new.confirm email address' %><%= text_field('user', 'email_confirmation',{:size => 50, :maxlength => 255, :tabindex => 2}) %>
<%= t 'user.new.not displayed publicly' %>
 
<%= t 'user.new.display name' %><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3}) %>
<%= t 'user.new.display name' %><%= text_field('user', 'display_name',{:size => 30, :maxlength => 255, :tabindex => 3, :value => @nickname}) %>
<%= t 'user.new.display name description' %>
 
<%= t 'user.new.password' %><%= password_field('user', 'pass_crypt',{:size => 30, :maxlength => 255, :tabindex => 4}) %>
<%= t 'user.new.confirm password' %><%= password_field('user', 'pass_crypt_confirmation',{:size => 30, :maxlength => 255, :tabindex => 5}) %>
 
<%= t 'user.new.openID' %><%= text_field('user', 'openid_url',{:size => 50, :maxlength => 255, :tabindex => 6, :value => @openID}) %>
<%= t 'user.new.openID description' %>
 
<% end %> diff --git a/config/environment.rb b/config/environment.rb index 539af83b2..01115ba97 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -52,6 +52,7 @@ Rails::Initializer.run do |config| config.gem 'rmagick', :lib => 'RMagick' config.gem 'oauth', :version => '>= 0.3.6' config.gem 'httpclient' + config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4' config.gem 'SystemTimer', :version => '>= 1.1.3', :lib => 'system_timer' config.gem 'sanitize' diff --git a/config/locales/en.yml b/config/locales/en.yml index 3f6518d4e..8053e710e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1508,11 +1508,18 @@ en: create_account: "create an account" email or username: "Email Address or Username:" password: "Password:" + openid: "OpenID:" + openid description: "Use your OpenID to login" + username_heading: "Login with username and password:" + openid_heading: "Login with an OpenID:" + or: "Or" remember: "Remember me:" lost password link: "Lost your password?" login_button: "Login" account not active: "Sorry, your account is not active yet.
Please click on the link in the account confirmation email to activate your account." auth failure: "Sorry, could not log in with those details." + openid missing provider: "Sorry, could not contact your OpenID provider" + openid invalid: "Sorry, your OpenID seems misformed" logout: title: "Logout" heading: "Logout from OpenStreetMap" @@ -1547,6 +1554,8 @@ en: display name description: "Your publicly displayed username. You can change this later in the preferences." password: "Password:" confirm password: "Confirm Password:" + openID: "OpenID:" + openID description: '(Optional) If you have an OpenID you can associate it with this account to login' signup: Signup flash create success message: "User was successfully created. Check your email for a confirmation note, and you will be mapping in no time :-)

Please note that you will not be able to login until you've received and confirmed your email address.

If you use an antispam system which sends confirmation requests then please make sure you whitelist webmaster@openstreetmap.org as we are unable to reply to any confirmation requests." no_such_user: @@ -1610,6 +1619,10 @@ en: current email address: "Current Email Address:" new email address: "New Email Address:" email never displayed publicly: "(never displayed publicly)" + openid: + openid: "OpenID:" + link: "http://wiki.openstreetmap.org/wiki/OpenID" + link text: "what is this?" public editing: heading: "Public editing:" enabled: "Enabled. Not anonymous and can edit data." diff --git a/db/migrate/050_add_open_id_authentication_tables.rb b/db/migrate/050_add_open_id_authentication_tables.rb new file mode 100644 index 000000000..7dfff209d --- /dev/null +++ b/db/migrate/050_add_open_id_authentication_tables.rb @@ -0,0 +1,30 @@ +class AddOpenIdAuthenticationTables < ActiveRecord::Migration + def self.up + create_table :open_id_authentication_associations, :force => true do |t| + t.integer :issued, :lifetime + t.string :handle, :assoc_type + t.binary :server_url, :secret + end + + create_table :open_id_authentication_nonces, :force => true do |t| + t.integer :timestamp, :null => false + t.string :server_url, :null => true + t.string :salt, :null => false + end + + add_column :users, :openid_url, :string + + add_index :users, [:openid_url], :name => "user_openid_unique_idx", :unique => true + add_index :open_id_authentication_associations, [:server_url], :name => "open_id_associations_server_url_idx" + add_index :open_id_authentication_nonces, [:timestamp], :name => "open_id_nonces_timestamp_idx" + end + + def self.down + remove_index :users, :name => "user_openid_unique_idx" + remove_index :open_id_authentication_associations, :name => "open_id_associations_server_url_idx" + remove_index :open_id_authentication_nonces, :name => "open_id_nonces_timestamp_idx" + remove_column :users, :openid_url + drop_table :open_id_authentication_associations + drop_table :open_id_authentication_nonces + end +end diff --git a/public/images/google.gif b/public/images/google.gif new file mode 100644 index 000000000..ed6ba1093 Binary files /dev/null and b/public/images/google.gif differ diff --git a/public/images/myopenid.png b/public/images/myopenid.png new file mode 100644 index 000000000..78e4562e4 Binary files /dev/null and b/public/images/myopenid.png differ diff --git a/public/images/myspace.png b/public/images/myspace.png new file mode 100644 index 000000000..2fefe48af Binary files /dev/null and b/public/images/myspace.png differ diff --git a/public/images/openid-inputicon.gif b/public/images/openid-inputicon.gif new file mode 100644 index 000000000..cde836c89 Binary files /dev/null and b/public/images/openid-inputicon.gif differ diff --git a/public/images/openid_logo.png b/public/images/openid_logo.png new file mode 100644 index 000000000..8a8a92453 Binary files /dev/null and b/public/images/openid_logo.png differ diff --git a/public/images/wordpress.png b/public/images/wordpress.png new file mode 100644 index 000000000..901549859 Binary files /dev/null and b/public/images/wordpress.png differ diff --git a/public/images/yahoo.gif b/public/images/yahoo.gif new file mode 100644 index 000000000..078f30971 Binary files /dev/null and b/public/images/yahoo.gif differ diff --git a/public/stylesheets/common.css b/public/stylesheets/common.css index b6ee99712..11a0d20ea 100644 --- a/public/stylesheets/common.css +++ b/public/stylesheets/common.css @@ -739,3 +739,15 @@ abbr.geo { .table1 { background: #fff; } + +/* Rules for Login page */ +.loginBox { + float: left; + width: 400px; + height: 200px; + margin-bottom: 40px; + border-style: solid; + border-width: 1px; + padding-left: 10px; + padding-right: 10px; +} diff --git a/public/stylesheets/small.css b/public/stylesheets/small.css index b11aebf69..82a93544b 100644 --- a/public/stylesheets/small.css +++ b/public/stylesheets/small.css @@ -109,3 +109,18 @@ h1 { width: 100%; max-width: 18em; } + +#loginForm input#user_openid_url { + width: 100%; + max-width: 18em; +} + +/* Rules for Login page */ +.loginBox { + float: left; + width: 90%; + border-style: solid; + border-width: 1px; + padding-left: 10px; + padding-right: 10px; +} \ No newline at end of file diff --git a/vendor/plugins/open_id_authentication/CHANGELOG b/vendor/plugins/open_id_authentication/CHANGELOG new file mode 100644 index 000000000..7349bd3c0 --- /dev/null +++ b/vendor/plugins/open_id_authentication/CHANGELOG @@ -0,0 +1,35 @@ +* Fake HTTP method from OpenID server since they only support a GET. Eliminates the need to set an extra route to match the server's reply. [Josh Peek] + +* OpenID 2.0 recommends that forms should use the field name "openid_identifier" rather than "openid_url" [Josh Peek] + +* Return open_id_response.display_identifier to the application instead of .endpoints.claimed_id. [nbibler] + +* Add Timeout protection [Rick] + +* An invalid identity url passed through authenticate_with_open_id will no longer raise an InvalidOpenId exception. Instead it will return Result[:missing] to the completion block. + +* Allow a return_to option to be used instead of the requested url [Josh Peek] + +* Updated plugin to use Ruby OpenID 2.x.x [Josh Peek] + +* Tied plugin to ruby-openid 1.1.4 gem until we can make it compatible with 2.x [DHH] + +* Use URI instead of regexps to normalize the URL and gain free, better matching #8136 [dkubb] + +* Allow -'s in #normalize_url [Rick] + +* remove instance of mattr_accessor, it was breaking tests since they don't load ActiveSupport. Fix Timeout test [Rick] + +* Throw a InvalidOpenId exception instead of just a RuntimeError when the URL can't be normalized [DHH] + +* Just use the path for the return URL, so extra query parameters don't interfere [DHH] + +* Added a new default database-backed store after experiencing trouble with the filestore on NFS. The file store is still available as an option [DHH] + +* Added normalize_url and applied it to all operations going through the plugin [DHH] + +* Removed open_id? as the idea of using the same input box for both OpenID and username has died -- use using_open_id? instead (which checks for the presence of params[:openid_url] by default) [DHH] + +* Added OpenIdAuthentication::Result to make it easier to deal with default situations where you don't care to do something particular for each error state [DHH] + +* Stop relying on root_url being defined, we can just grab the current url instead [DHH] \ No newline at end of file diff --git a/vendor/plugins/open_id_authentication/README b/vendor/plugins/open_id_authentication/README new file mode 100644 index 000000000..807cdc756 --- /dev/null +++ b/vendor/plugins/open_id_authentication/README @@ -0,0 +1,231 @@ +OpenIdAuthentication +==================== + +Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first: + + gem install ruby-openid + +To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb +from that gem. + +The specification used is http://openid.net/specs/openid-authentication-2_0.html. + + +Prerequisites +============= + +OpenID authentication uses the session, so be sure that you haven't turned that off. It also relies on a number of +database tables to store the authentication keys. So you'll have to run the migration to create these before you get started: + + rake open_id_authentication:db:create + +Or, use the included generators to install or upgrade: + + ./script/generate open_id_authentication_tables MigrationName + ./script/generate upgrade_open_id_authentication_tables MigrationName + +Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb: + + OpenIdAuthentication.store = :file + +This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations. +If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb. + +The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb: + + map.root :controller => 'articles' + +This plugin relies on Rails Edge revision 6317 or newer. + + +Example +======= + +This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add +salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point, +not a destination. + +Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever +model you are using for authentication. + +Also of note is the following code block used in the example below: + + authenticate_with_open_id do |result, identity_url| + ... + end + +In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' - +If you are storing just 'example.com' with your user, the lookup will fail. + +There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs. + + OpenIdAuthentication.normalize_url(user.identity_url) + +The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/' +It will also raise an InvalidOpenId exception if the URL is determined to not be valid. +Use the above code in your User model and validate OpenID URLs before saving them. + +config/routes.rb + + map.root :controller => 'articles' + map.resource :session + + +app/views/sessions/new.erb + + <% form_tag(session_url) do %> +

+ + <%= text_field_tag "name" %> +

+ +

+ + <%= password_field_tag %> +

+ +

+ ...or use: +

+ +

+ + <%= text_field_tag "openid_identifier" %> +

+ +

+ <%= submit_tag 'Sign in', :disable_with => "Signing in…" %> +

+ <% end %> + +app/controllers/sessions_controller.rb + class SessionsController < ApplicationController + def create + if using_open_id? + open_id_authentication + else + password_authentication(params[:name], params[:password]) + end + end + + + protected + def password_authentication(name, password) + if @current_user = @account.users.authenticate(params[:name], params[:password]) + successful_login + else + failed_login "Sorry, that username/password doesn't work" + end + end + + def open_id_authentication + authenticate_with_open_id do |result, identity_url| + if result.successful? + if @current_user = @account.users.find_by_identity_url(identity_url) + successful_login + else + failed_login "Sorry, no user by that identity URL exists (#{identity_url})" + end + else + failed_login result.message + end + end + end + + + private + def successful_login + session[:user_id] = @current_user.id + redirect_to(root_url) + end + + def failed_login(message) + flash[:error] = message + redirect_to(new_session_url) + end + end + + + +If you're fine with the result messages above and don't need individual logic on a per-failure basis, +you can collapse the case into a mere boolean: + + def open_id_authentication + authenticate_with_open_id do |result, identity_url| + if result.successful? && @current_user = @account.users.find_by_identity_url(identity_url) + successful_login + else + failed_login(result.message || "Sorry, no user by that identity URL exists (#{identity_url})") + end + end + end + + +Simple Registration OpenID Extension +==================================== + +Some OpenID Providers support this lightweight profile exchange protocol. See more: http://www.openidenabled.com/openid/simple-registration-extension + +You can support it in your app by changing #open_id_authentication + + def open_id_authentication(identity_url) + # Pass optional :required and :optional keys to specify what sreg fields you want. + # Be sure to yield registration, a third argument in the #authenticate_with_open_id block. + authenticate_with_open_id(identity_url, + :required => [ :nickname, :email ], + :optional => :fullname) do |result, identity_url, registration| + case result.status + when :missing + failed_login "Sorry, the OpenID server couldn't be found" + when :invalid + failed_login "Sorry, but this does not appear to be a valid OpenID" + when :canceled + failed_login "OpenID verification was canceled" + when :failed + failed_login "Sorry, the OpenID verification failed" + when :successful + if @current_user = @account.users.find_by_identity_url(identity_url) + assign_registration_attributes!(registration) + + if current_user.save + successful_login + else + failed_login "Your OpenID profile registration failed: " + + @current_user.errors.full_messages.to_sentence + end + else + failed_login "Sorry, no user by that identity URL exists" + end + end + end + end + + # registration is a hash containing the valid sreg keys given above + # use this to map them to fields of your user model + def assign_registration_attributes!(registration) + model_to_registration_mapping.each do |model_attribute, registration_attribute| + unless registration[registration_attribute].blank? + @current_user.send("#{model_attribute}=", registration[registration_attribute]) + end + end + end + + def model_to_registration_mapping + { :login => 'nickname', :email => 'email', :display_name => 'fullname' } + end + +Attribute Exchange OpenID Extension +=================================== + +Some OpenID providers also support the OpenID AX (attribute exchange) protocol for exchanging identity information between endpoints. See more: http://openid.net/specs/openid-attribute-exchange-1_0.html + +Accessing AX data is very similar to the Simple Registration process, described above -- just add the URI identifier for the AX field to your :optional or :required parameters. For example: + + authenticate_with_open_id(identity_url, + :required => [ :email, 'http://schema.openid.net/birthDate' ]) do |result, identity_url, registration| + +This would provide the sreg data for :email, and the AX data for 'http://schema.openid.net/birthDate' + + + +Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license \ No newline at end of file diff --git a/vendor/plugins/open_id_authentication/Rakefile b/vendor/plugins/open_id_authentication/Rakefile new file mode 100644 index 000000000..31074b856 --- /dev/null +++ b/vendor/plugins/open_id_authentication/Rakefile @@ -0,0 +1,22 @@ +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +desc 'Default: run unit tests.' +task :default => :test + +desc 'Test the open_id_authentication plugin.' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +desc 'Generate documentation for the open_id_authentication plugin.' +Rake::RDocTask.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'OpenIdAuthentication' + rdoc.options << '--line-numbers' << '--inline-source' + rdoc.rdoc_files.include('README') + rdoc.rdoc_files.include('lib/**/*.rb') +end diff --git a/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb new file mode 100644 index 000000000..6f78afc71 --- /dev/null +++ b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/open_id_authentication_tables_generator.rb @@ -0,0 +1,11 @@ +class OpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase + def initialize(runtime_args, runtime_options = {}) + super + end + + def manifest + record do |m| + m.migration_template 'migration.rb', 'db/migrate' + end + end +end diff --git a/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb new file mode 100644 index 000000000..ef2a0cfb4 --- /dev/null +++ b/vendor/plugins/open_id_authentication/generators/open_id_authentication_tables/templates/migration.rb @@ -0,0 +1,20 @@ +class <%= class_name %> < ActiveRecord::Migration + def self.up + create_table :open_id_authentication_associations, :force => true do |t| + t.integer :issued, :lifetime + t.string :handle, :assoc_type + t.binary :server_url, :secret + end + + create_table :open_id_authentication_nonces, :force => true do |t| + t.integer :timestamp, :null => false + t.string :server_url, :null => true + t.string :salt, :null => false + end + end + + def self.down + drop_table :open_id_authentication_associations + drop_table :open_id_authentication_nonces + end +end diff --git a/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb new file mode 100644 index 000000000..d13bbab23 --- /dev/null +++ b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/templates/migration.rb @@ -0,0 +1,26 @@ +class <%= class_name %> < ActiveRecord::Migration + def self.up + drop_table :open_id_authentication_settings + drop_table :open_id_authentication_nonces + + create_table :open_id_authentication_nonces, :force => true do |t| + t.integer :timestamp, :null => false + t.string :server_url, :null => true + t.string :salt, :null => false + end + end + + def self.down + drop_table :open_id_authentication_nonces + + create_table :open_id_authentication_nonces, :force => true do |t| + t.integer :created + t.string :nonce + end + + create_table :open_id_authentication_settings, :force => true do |t| + t.string :setting + t.binary :value + end + end +end diff --git a/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb new file mode 100644 index 000000000..02fddd7fd --- /dev/null +++ b/vendor/plugins/open_id_authentication/generators/upgrade_open_id_authentication_tables/upgrade_open_id_authentication_tables_generator.rb @@ -0,0 +1,11 @@ +class UpgradeOpenIdAuthenticationTablesGenerator < Rails::Generator::NamedBase + def initialize(runtime_args, runtime_options = {}) + super + end + + def manifest + record do |m| + m.migration_template 'migration.rb', 'db/migrate' + end + end +end diff --git a/vendor/plugins/open_id_authentication/init.rb b/vendor/plugins/open_id_authentication/init.rb new file mode 100644 index 000000000..808c7bdbd --- /dev/null +++ b/vendor/plugins/open_id_authentication/init.rb @@ -0,0 +1,18 @@ +if config.respond_to?(:gems) + config.gem 'ruby-openid', :lib => 'openid', :version => '>=2.0.4' +else + begin + require 'openid' + rescue LoadError + begin + gem 'ruby-openid', '>=2.0.4' + rescue Gem::LoadError + puts "Install the ruby-openid gem to enable OpenID support" + end + end +end + +config.to_prepare do + OpenID::Util.logger = Rails.logger + ActionController::Base.send :include, OpenIdAuthentication +end diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb new file mode 100644 index 000000000..b485c5fe3 --- /dev/null +++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication.rb @@ -0,0 +1,240 @@ +require 'uri' +require 'openid/extensions/sreg' +require 'openid/extensions/ax' +require 'openid/store/filesystem' + +require File.dirname(__FILE__) + '/open_id_authentication/association' +require File.dirname(__FILE__) + '/open_id_authentication/nonce' +require File.dirname(__FILE__) + '/open_id_authentication/db_store' +require File.dirname(__FILE__) + '/open_id_authentication/request' +require File.dirname(__FILE__) + '/open_id_authentication/timeout_fixes' if OpenID::VERSION == "2.0.4" + +module OpenIdAuthentication + OPEN_ID_AUTHENTICATION_DIR = RAILS_ROOT + "/tmp/openids" + + def self.store + @@store + end + + def self.store=(*store_option) + store, *parameters = *([ store_option ].flatten) + + @@store = case store + when :db + OpenIdAuthentication::DbStore.new + when :file + OpenID::Store::Filesystem.new(OPEN_ID_AUTHENTICATION_DIR) + else + store + end + end + + self.store = :db + + class InvalidOpenId < StandardError + end + + class Result + ERROR_MESSAGES = { + :missing => "Sorry, the OpenID server couldn't be found", + :invalid => "Sorry, but this does not appear to be a valid OpenID", + :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 status + @code + 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 + + # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization + def self.normalize_identifier(identifier) + # clean up whitespace + identifier = identifier.to_s.strip + + # if an XRI has a prefix, strip it. + identifier.gsub!(/xri:\/\//i, '') + + # dodge XRIs -- TODO: validate, don't just skip. + unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0)) + # does it begin with http? if not, add it. + identifier = "http://#{identifier}" unless identifier =~ /^http/i + + # strip any fragments + identifier.gsub!(/\#(.*)$/, '') + + begin + uri = URI.parse(identifier) + uri.scheme = uri.scheme.downcase # URI should do this + identifier = uri.normalize.to_s + rescue URI::InvalidURIError + raise InvalidOpenId.new("#{identifier} is not an OpenID identifier") + end + end + + return identifier + end + + # deprecated for OpenID 2.0, where not all OpenIDs are URLs + def self.normalize_url(url) + ActiveSupport::Deprecation.warn "normalize_url has been deprecated, use normalize_identifier instead" + self.normalize_identifier(url) + end + + protected + def normalize_url(url) + OpenIdAuthentication.normalize_url(url) + end + + def normalize_identifier(url) + OpenIdAuthentication.normalize_identifier(url) + end + + # The parameter name of "openid_identifier" is used rather than the Rails convention "open_id_identifier" + # because that's what the specification dictates in order to get browser auto-complete working across sites + def using_open_id?(identity_url = nil) #:doc: + identity_url ||= params[:openid_identifier] || params[:openid_url] + !identity_url.blank? || params[:open_id_complete] + end + + def authenticate_with_open_id(identity_url = nil, options = {}, &block) #:doc: + identity_url ||= params[:openid_identifier] || params[:openid_url] + + if params[:open_id_complete].nil? + begin_open_id_authentication(identity_url, options, &block) + else + complete_open_id_authentication(&block) + end + end + + private + def begin_open_id_authentication(identity_url, options = {}) + identity_url = normalize_identifier(identity_url) + return_to = options.delete(:return_to) + method = options.delete(:method) + + options[:required] ||= [] # reduces validation later + options[:optional] ||= [] + + open_id_request = open_id_consumer.begin(identity_url) + add_simple_registration_fields(open_id_request, options) + add_ax_fields(open_id_request, options) + redirect_to(open_id_redirect_url(open_id_request, return_to, method)) + rescue OpenIdAuthentication::InvalidOpenId => e + yield Result[:invalid], identity_url, nil + 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_identifier(open_id_response.display_identifier) if open_id_response.display_identifier + + case open_id_response.status + when OpenID::Consumer::SUCCESS + profile_data = {} + + # merge the SReg data and the AX data into a single hash of profile data + [ OpenID::SReg::Response, OpenID::AX::FetchResponse ].each do |data_response| + if data_response.from_success_response( open_id_response ) + profile_data.merge! data_response.from_success_response( open_id_response ).data + end + end + + yield Result[:successful], identity_url, profile_data + 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, OpenIdAuthentication.store) + end + + def add_simple_registration_fields(open_id_request, fields) + sreg_request = OpenID::SReg::Request.new + + # filter out AX identifiers (URIs) + required_fields = fields[:required].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact + optional_fields = fields[:optional].collect { |f| f.to_s unless f =~ /^https?:\/\// }.compact + + sreg_request.request_fields(required_fields, true) unless required_fields.blank? + sreg_request.request_fields(optional_fields, false) unless optional_fields.blank? + sreg_request.policy_url = fields[:policy_url] if fields[:policy_url] + open_id_request.add_extension(sreg_request) + end + + def add_ax_fields( open_id_request, fields ) + ax_request = OpenID::AX::FetchRequest.new + + # look through the :required and :optional fields for URIs (AX identifiers) + fields[:required].each do |f| + next unless f =~ /^https?:\/\// + ax_request.add( OpenID::AX::AttrInfo.new( f, nil, true ) ) + end + + fields[:optional].each do |f| + next unless f =~ /^https?:\/\// + ax_request.add( OpenID::AX::AttrInfo.new( f, nil, false ) ) + end + + open_id_request.add_extension( ax_request ) + end + + def open_id_redirect_url(open_id_request, return_to = nil, method = nil) + open_id_request.return_to_args['_method'] = (method || request.method).to_s + open_id_request.return_to_args['open_id_complete'] = '1' + open_id_request.redirect_url(root_url, return_to || requested_url) + end + + def requested_url + relative_url_root = self.class.respond_to?(:relative_url_root) ? + self.class.relative_url_root.to_s : + request.relative_url_root + "#{request.protocol}#{request.host_with_port}#{ActionController::Base.relative_url_root}#{request.path}" + 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 diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb new file mode 100644 index 000000000..9654eaeb2 --- /dev/null +++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/association.rb @@ -0,0 +1,9 @@ +module OpenIdAuthentication + class Association < ActiveRecord::Base + set_table_name :open_id_authentication_associations + + def from_record + OpenID::Association.new(handle, secret, issued, lifetime, assoc_type) + end + end +end diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb new file mode 100644 index 000000000..780fb6ad2 --- /dev/null +++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/db_store.rb @@ -0,0 +1,55 @@ +require 'openid/store/interface' + +module OpenIdAuthentication + class DbStore < OpenID::Store::Interface + def self.cleanup_nonces + now = Time.now.to_i + Nonce.delete_all(["timestamp > ? OR timestamp < ?", now + OpenID::Nonce.skew, now - OpenID::Nonce.skew]) + end + + def self.cleanup_associations + now = Time.now.to_i + Association.delete_all(['issued + lifetime > ?',now]) + end + + def store_association(server_url, assoc) + remove_association(server_url, assoc.handle) + Association.create(:server_url => server_url, + :handle => assoc.handle, + :secret => assoc.secret, + :issued => assoc.issued, + :lifetime => assoc.lifetime, + :assoc_type => assoc.assoc_type) + end + + def get_association(server_url, handle = nil) + assocs = if handle.blank? + Association.find_all_by_server_url(server_url) + else + Association.find_all_by_server_url_and_handle(server_url, handle) + end + + assocs.reverse.each do |assoc| + a = assoc.from_record + if a.expires_in == 0 + assoc.destroy + else + return a + end + end if assocs.any? + + return nil + end + + def remove_association(server_url, handle) + Association.delete_all(['server_url = ? AND handle = ?', server_url, handle]) > 0 + end + + def use_nonce(server_url, timestamp, salt) + return false if Nonce.find_by_server_url_and_timestamp_and_salt(server_url, timestamp, salt) + return false if (timestamp - Time.now.to_i).abs > OpenID::Nonce.skew + Nonce.create(:server_url => server_url, :timestamp => timestamp, :salt => salt) + return true + end + end +end diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb new file mode 100644 index 000000000..c52f6c50d --- /dev/null +++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/nonce.rb @@ -0,0 +1,5 @@ +module OpenIdAuthentication + class Nonce < ActiveRecord::Base + set_table_name :open_id_authentication_nonces + end +end diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb new file mode 100644 index 000000000..e0cc8e3fc --- /dev/null +++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/request.rb @@ -0,0 +1,23 @@ +module OpenIdAuthentication + module Request + def self.included(base) + base.alias_method_chain :request_method, :openid + end + + def request_method_with_openid + if !parameters[:_method].blank? && parameters[:open_id_complete] == '1' + parameters[:_method].to_sym + else + request_method_without_openid + end + end + end +end + +# In Rails 2.3, the request object has been renamed +# from AbstractRequest to Request +if defined? ActionController::Request + ActionController::Request.send :include, OpenIdAuthentication::Request +else + ActionController::AbstractRequest.send :include, OpenIdAuthentication::Request +end diff --git a/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb b/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb new file mode 100644 index 000000000..cc711c9ac --- /dev/null +++ b/vendor/plugins/open_id_authentication/lib/open_id_authentication/timeout_fixes.rb @@ -0,0 +1,20 @@ +# http://trac.openidenabled.com/trac/ticket/156 +module OpenID + @@timeout_threshold = 20 + + def self.timeout_threshold + @@timeout_threshold + end + + def self.timeout_threshold=(value) + @@timeout_threshold = value + end + + class StandardFetcher + def make_http(uri) + http = @proxy.new(uri.host, uri.port) + http.read_timeout = http.open_timeout = OpenID.timeout_threshold + http + end + end +end \ No newline at end of file diff --git a/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake b/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake new file mode 100644 index 000000000..c71434a50 --- /dev/null +++ b/vendor/plugins/open_id_authentication/tasks/open_id_authentication_tasks.rake @@ -0,0 +1,30 @@ +namespace :open_id_authentication do + namespace :db do + desc "Creates authentication tables for use with OpenIdAuthentication" + task :create => :environment do + generate_migration(["open_id_authentication_tables", "add_open_id_authentication_tables"]) + end + + desc "Upgrade authentication tables from ruby-openid 1.x.x to 2.x.x" + task :upgrade => :environment do + generate_migration(["upgrade_open_id_authentication_tables", "upgrade_open_id_authentication_tables"]) + end + + def generate_migration(args) + require 'rails_generator' + require 'rails_generator/scripts/generate' + + if ActiveRecord::Base.connection.supports_migrations? + Rails::Generator::Scripts::Generate.new.run(args) + else + raise "Task unavailable to this database (no migration support)" + end + end + + desc "Clear the authentication tables" + task :clear => :environment do + OpenIdAuthentication::DbStore.cleanup_nonces + OpenIdAuthentication::DbStore.cleanup_associations + end + end +end diff --git a/vendor/plugins/open_id_authentication/test/normalize_test.rb b/vendor/plugins/open_id_authentication/test/normalize_test.rb new file mode 100644 index 000000000..635d3abc9 --- /dev/null +++ b/vendor/plugins/open_id_authentication/test/normalize_test.rb @@ -0,0 +1,32 @@ +require File.dirname(__FILE__) + '/test_helper' + +class NormalizeTest < Test::Unit::TestCase + include OpenIdAuthentication + + NORMALIZATIONS = { + "openid.aol.com/nextangler" => "http://openid.aol.com/nextangler", + "http://openid.aol.com/nextangler" => "http://openid.aol.com/nextangler", + "https://openid.aol.com/nextangler" => "https://openid.aol.com/nextangler", + "HTTP://OPENID.AOL.COM/NEXTANGLER" => "http://openid.aol.com/NEXTANGLER", + "HTTPS://OPENID.AOL.COM/NEXTANGLER" => "https://openid.aol.com/NEXTANGLER", + "loudthinking.com" => "http://loudthinking.com/", + "http://loudthinking.com" => "http://loudthinking.com/", + "http://loudthinking.com:80" => "http://loudthinking.com/", + "https://loudthinking.com:443" => "https://loudthinking.com/", + "http://loudthinking.com:8080" => "http://loudthinking.com:8080/", + "techno-weenie.net" => "http://techno-weenie.net/", + "http://techno-weenie.net" => "http://techno-weenie.net/", + "http://techno-weenie.net " => "http://techno-weenie.net/", + "=name" => "=name" + } + + def test_normalizations + NORMALIZATIONS.each do |from, to| + assert_equal to, normalize_identifier(from) + end + end + + def test_broken_open_id + assert_raises(InvalidOpenId) { normalize_identifier(nil) } + end +end diff --git a/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb b/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb new file mode 100644 index 000000000..ddcc17b96 --- /dev/null +++ b/vendor/plugins/open_id_authentication/test/open_id_authentication_test.rb @@ -0,0 +1,46 @@ +require File.dirname(__FILE__) + '/test_helper' + +class OpenIdAuthenticationTest < Test::Unit::TestCase + def setup + @controller = Class.new do + include OpenIdAuthentication + def params() {} end + end.new + end + + def test_authentication_should_fail_when_the_identity_server_is_missing + open_id_consumer = mock() + open_id_consumer.expects(:begin).raises(OpenID::OpenIDError) + @controller.expects(:open_id_consumer).returns(open_id_consumer) + @controller.expects(:logger).returns(mock(:error => true)) + + @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url| + assert result.missing? + assert_equal "Sorry, the OpenID server couldn't be found", result.message + end + end + + def test_authentication_should_be_invalid_when_the_identity_url_is_invalid + @controller.send(:authenticate_with_open_id, "!") do |result, identity_url| + assert result.invalid?, "Result expected to be invalid but was not" + assert_equal "Sorry, but this does not appear to be a valid OpenID", result.message + end + end + + def test_authentication_should_fail_when_the_identity_server_times_out + open_id_consumer = mock() + open_id_consumer.expects(:begin).raises(Timeout::Error, "Identity Server took too long.") + @controller.expects(:open_id_consumer).returns(open_id_consumer) + @controller.expects(:logger).returns(mock(:error => true)) + + @controller.send(:authenticate_with_open_id, "http://someone.example.com") do |result, identity_url| + assert result.missing? + assert_equal "Sorry, the OpenID server couldn't be found", result.message + end + end + + def test_authentication_should_begin_when_the_identity_server_is_present + @controller.expects(:begin_open_id_authentication) + @controller.send(:authenticate_with_open_id, "http://someone.example.com") + end +end diff --git a/vendor/plugins/open_id_authentication/test/status_test.rb b/vendor/plugins/open_id_authentication/test/status_test.rb new file mode 100644 index 000000000..b1d5e0933 --- /dev/null +++ b/vendor/plugins/open_id_authentication/test/status_test.rb @@ -0,0 +1,14 @@ +require File.dirname(__FILE__) + '/test_helper' + +class StatusTest < Test::Unit::TestCase + include OpenIdAuthentication + + def test_state_conditional + assert Result[:missing].missing? + assert Result[:missing].unsuccessful? + assert !Result[:missing].successful? + + assert Result[:successful].successful? + assert !Result[:successful].unsuccessful? + end +end \ No newline at end of file diff --git a/vendor/plugins/open_id_authentication/test/test_helper.rb b/vendor/plugins/open_id_authentication/test/test_helper.rb new file mode 100644 index 000000000..43216e1ef --- /dev/null +++ b/vendor/plugins/open_id_authentication/test/test_helper.rb @@ -0,0 +1,17 @@ +require 'test/unit' +require 'rubygems' + +gem 'activesupport' +require 'active_support' + +gem 'actionpack' +require 'action_controller' + +gem 'mocha' +require 'mocha' + +gem 'ruby-openid' +require 'openid' + +RAILS_ROOT = File.dirname(__FILE__) unless defined? RAILS_ROOT +require File.dirname(__FILE__) + "/../lib/open_id_authentication"