<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>db/migrate/20081019163427_add_open_id_authentication_tables.rb</filename>
    </added>
    <added>
      <filename>db/migrate/20081019163720_add_open_id_url_to_people.rb</filename>
    </added>
    <added>
      <filename>db/migrate/20081019180856_add_unique_indexes_to_people_on_email_and_open_id_url.rb</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -7,4 +7,21 @@ class PeopleController &lt; ResourceController::Base
     flash      &quot;Thanks for signing up!&quot;
     wants.html { redirect_back_or_default(root_url) }
   end
+
+  def signup_with_open_id 
+    authenticate_with_open_id do |result, identity_url|
+      @person = Person.new(params[:person])
+      @person.open_id_url = identity_url
+
+      @person.open_id_url_authenticated = true if result.successful?
+      @person.open_id_url_message       = result.message
+
+      if @person.save
+        self.current_person = @person
+        redirect_back_or_default(root_url)
+      else
+        render :action =&gt; 'new'
+      end
+    end
+  end
 end</diff>
      <filename>app/controllers/people_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -3,13 +3,9 @@ class SessionsController &lt; ApplicationController
   def create
     logout_keeping_session!
     person = Person.authenticate(params[:email], params[:password])
+
     if person
-      self.current_person = person
-      new_cookie_flag     = (params[:remember_me] == &quot;1&quot;)
-      handle_remember_cookie! new_cookie_flag
-      
-      flash[:notice] = &quot;Logged in successfully&quot;
-      redirect_back_or_default root_url
+      successful_login(person)
     else
       note_failed_signin
       @email       = params[:email]
@@ -18,6 +14,21 @@ class SessionsController &lt; ApplicationController
     end
   end
 
+  def create_with_open_id
+    logout_keeping_session!
+
+    authenticate_with_open_id do |result, identity_url|
+      if result.successful? &amp;&amp; person = Person.find_by_open_id_url(identity_url)
+        successful_login(person)
+      else
+        flash[:error] = &quot;Could not log you in as #{identity_url}.&quot;
+        flash[:error] &lt;&lt; &quot; Message: #{result.message}&quot; unless result.successful?
+
+        render :action =&gt; 'new'
+      end
+    end
+  end
+
   def destroy
     logout_killing_session!
     flash[:notice] = &quot;You have been logged out.&quot;
@@ -30,4 +41,13 @@ protected
     flash[:error] = &quot;Couldn't log you in as '#{params[:email]}'&quot;
     logger.warn &quot;Failed login for '#{params[:email]}' from #{request.remote_ip} at #{Time.now.utc}&quot;
   end
+
+  def successful_login(person)
+    self.current_person = person
+    new_cookie_flag     = (params[:remember_me] == &quot;1&quot;)
+    handle_remember_cookie! new_cookie_flag
+
+    flash[:notice] = &quot;Logged in successfully&quot;
+    redirect_back_or_default root_url
+  end
 end</diff>
      <filename>app/controllers/sessions_controller.rb</filename>
    </modified>
    <modified>
      <diff>@@ -13,31 +13,44 @@ class Person &lt; ActiveRecord::Base
   validates_uniqueness_of   :email,    :case_sensitive =&gt; false
   validates_format_of       :email,    :with =&gt; RE_EMAIL_OK, :message =&gt; MSG_EMAIL_BAD
 
-  attr_accessible :email, :name, :password, :password_confirmation
+  validates_uniqueness_of   :open_id_url, :if =&gt; :open_id_url?
+  validate                  :validate_open_id_url_authentication, :if =&gt; :open_id_url?
+
+  attr_accessible :email, :name, :password, :password_confirmation, :open_id_url
+  attr_accessor :open_id_url_authenticated, :open_id_url_message
 
   class &lt;&lt; self
     def authenticate(email, password)
       u = find_by_email(email) # need to get the salt
       u &amp;&amp; u.authenticated?(password) ? u : nil
     end
-    
+
     def find_by_valid_password_reset_code(code)
       u = find_by_password_reset_code(code)
       u &amp;&amp; u.password_reset_code_expires &amp;&amp; u.password_reset_code_expires.after?(Time.now) ? u : nil
     end
   end
-  
+
   def create_password_reset_code
     self[:password_reset_code]         = self.class.make_token
     self[:password_reset_code_expires] = 1.week.from_now
     save(false)
-    
+
     PasswordResetMailer.deliver_password_reset(self)
   end
-  
+
   def expire_password_reset_code
     self[:password_reset_code]         = nil
     self[:password_reset_code_expires] = nil
     save(false)
   end
+
+  def open_id_url_authenticated?
+    !!open_id_url_authenticated
+  end
+
+  protected
+  def validate_open_id_url_authentication
+    errors.add(:open_id_url, open_id_url_message) if new_record? &amp;&amp; !open_id_url_authenticated?
+  end
 end</diff>
      <filename>app/models/person.rb</filename>
    </modified>
    <modified>
      <diff>@@ -12,6 +12,7 @@ ActionController::Routing::Routes.draw do |map|
   
   map.connect '/account', :controller =&gt; 'accounts', :action =&gt; 'edit'
   
-  map.resources :people
-  map.resource  :session, :account
+  map.resource  :account
+  map.resources :people,  :collection =&gt; {:signup_with_open_id =&gt; :any}
+  map.resource  :session, :member =&gt; {:create_with_open_id =&gt; :any}
 end</diff>
      <filename>config/routes.rb</filename>
    </modified>
    <modified>
      <diff>@@ -9,7 +9,22 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version =&gt; 20080922172907) do
+ActiveRecord::Schema.define(:version =&gt; 20081019180856) do
+
+  create_table &quot;open_id_authentication_associations&quot;, :force =&gt; true do |t|
+    t.integer &quot;issued&quot;
+    t.integer &quot;lifetime&quot;
+    t.string  &quot;handle&quot;
+    t.string  &quot;assoc_type&quot;
+    t.binary  &quot;server_url&quot;
+    t.binary  &quot;secret&quot;
+  end
+
+  create_table &quot;open_id_authentication_nonces&quot;, :force =&gt; true do |t|
+    t.integer &quot;timestamp&quot;,  :null =&gt; false
+    t.string  &quot;server_url&quot;
+    t.string  &quot;salt&quot;,       :null =&gt; false
+  end
 
   create_table &quot;people&quot;, :force =&gt; true do |t|
     t.string   &quot;name&quot;,                        :limit =&gt; 100, :default =&gt; &quot;&quot;
@@ -22,6 +37,10 @@ ActiveRecord::Schema.define(:version =&gt; 20080922172907) do
     t.datetime &quot;remember_token_expires_at&quot;
     t.string   &quot;password_reset_code&quot;
     t.datetime &quot;password_reset_code_expires&quot;
+    t.string   &quot;open_id_url&quot;
   end
 
+  add_index &quot;people&quot;, [&quot;email&quot;], :name =&gt; &quot;index_people_on_email&quot;, :unique =&gt; true
+  add_index &quot;people&quot;, [&quot;open_id_url&quot;], :name =&gt; &quot;index_people_on_open_id_url&quot;, :unique =&gt; true
+
 end</diff>
      <filename>db/schema.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,6 +6,10 @@ Factory.sequence :email do |n|
   &quot;name#{n}@host.com&quot;
 end
 
+Factory.sequence :open_id_url do |n|
+  &quot;http://openid#{n}.factory.com&quot;
+end
+
 Factory.define :person do |p|
   p.name                  { Factory.next :name }
   p.email                 { Factory.next :email }
@@ -13,6 +17,11 @@ Factory.define :person do |p|
   p.password_confirmation &quot;monkey&quot;
 end
 
+Factory.define :person_with_open_id, :parent =&gt; :person do |p|
+  p.open_id_url               { Factory.next :open_id_url }
+  p.open_id_url_authenticated true
+end
+
 Factory.define :remembered_person, :parent =&gt; :person do |p|
   p.remember_token            { Person.make_token }
   p.remember_token_expires_at 10.days.from_now</diff>
      <filename>test/factories.rb</filename>
    </modified>
    <modified>
      <diff>@@ -35,5 +35,31 @@ class PeopleControllerTest &lt; ActionController::TestCase
         assert_not_nil session[:person_id]
       end
     end
+
+    context &quot;Signing up with a valid OpenID&quot; do
+      setup do
+        stub_open_id(true, 'you rock')
+        post :signup_with_open_id, :openid_url =&gt; 'http://jamesgolick.com', :person =&gt; {:email =&gt; &quot;james@giraffesoft.ca&quot;, :name =&gt; &quot;james&quot;, :password =&gt; &quot;monkey&quot;, :password_confirmation =&gt; &quot;monkey&quot;}
+      end
+
+      should &quot;create a person and associate the OpenID URL with that person&quot; do
+        assert_not_nil Person.find_by_open_id_url('http://jamesgolick.com')
+      end
+
+      should_redirect_to(&quot;the homepage&quot;) { root_url }
+    end
+
+    context &quot;When signing up with an invalid openid url&quot; do
+      setup do
+        Person.find_by_open_id_url('http://jamesgolick.com').andand.destroy
+        stub_open_id(false, 'you suck')
+        post :signup_with_open_id, :openid_url =&gt; 'http://jamesgolick.com'
+      end
+
+      should_respond_with :success
+      should_render_template 'new'
+
+      should_not_change &quot;Person.count&quot;
+    end
   end
 end</diff>
      <filename>test/functional/people_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -6,9 +6,7 @@ class SessionsControllerTest &lt; ActionController::TestCase
       assert_equal @person, current_person
     end
 
-    should &quot;redirect&quot; do
-      assert_response :redirect
-    end
+    should_respond_with :redirect
   end
 
   context &quot;Given an existing person&quot; do
@@ -40,27 +38,27 @@ class SessionsControllerTest &lt; ActionController::TestCase
       end
     end
 
-    context &quot;on get to :destroy&quot; do
+    context &quot;when already logged in and with a remembered cookie&quot; do
       setup do
         @request.cookies[&quot;auth_token&quot;] = cookie_for(@person)
         login_as @person
-        get :destroy
       end
 
-      should &quot;redirect&quot; do
-        assert_response :redirect
-      end
+      context &quot;on get to :destroy&quot; do
+        setup do
+          get :destroy
+        end
 
-      should &quot;log the user out&quot; do
-        assert_nil session[:person_id]
-      end
+        should_respond_with :redirect
+        should_change &quot;session[:person_id]&quot;, :to =&gt; nil
 
-      should &quot;delete the login token&quot; do
-        assert @response.cookies[&quot;auth_token&quot;].blank?
+        should &quot;delete the login token&quot; do
+          assert @response.cookies[&quot;auth_token&quot;].blank?
+        end
       end
     end
   end
-  
+
   context &quot;on POST to create with invalid credentials&quot; do
     setup do
       post :create, :email =&gt; 'whatever', :password =&gt; 'nothign'
@@ -73,7 +71,7 @@ class SessionsControllerTest &lt; ActionController::TestCase
       assert_nil session[:person_id]      
     end
   end
-  
+
   context &quot;Given a remembered person&quot; do
     setup do
       @person = Factory(:remembered_person)
@@ -113,6 +111,48 @@ class SessionsControllerTest &lt; ActionController::TestCase
     end
   end
 
+  context &quot;Given a user with OpenID credentials stored&quot; do
+    setup do
+      @person = Factory(:person_with_open_id)
+    end
+
+    context &quot;with a valid openid_url&quot; do
+      setup do
+        stub_open_id(true, '', @person.open_id_url)
+        post :create_with_open_id, :openid_url =&gt; @person.open_id_url
+      end
+
+      should &quot;log that user in&quot; do
+        assert_equal @person, current_person
+      end
+
+      should_respond_with :redirect
+      should_redirect_to(&quot;the homepage&quot;) { root_url }
+    end
+
+    context &quot;with an invalid openid_url&quot; do
+      setup do
+        stub_open_id(false, 'fail')
+        post :create_with_open_id, :openid_url =&gt; 'http://whatever.com'
+      end
+
+      should_respond_with :success
+      should_render_template 'new'
+      should_set_the_flash_to(/fail/i)
+    end
+
+    context &quot;With a valid openid that has no user associated with it&quot; do
+      setup do
+        stub_open_id(true, '')
+        post :create_with_open_id, :openid_url =&gt; 'http://nobodyhasthisurl.com'
+      end
+
+      should_respond_with :success
+      should_render_template 'new'
+      should_set_the_flash_to(/could not log you in as/i)
+    end
+  end
+
   protected
     def auth_token(token)
       CGI::Cookie.new('name' =&gt; 'auth_token', 'value' =&gt; token)</diff>
      <filename>test/functional/sessions_controller_test.rb</filename>
    </modified>
    <modified>
      <diff>@@ -18,4 +18,8 @@ class ActiveSupport::TestCase
     def set_mailer_host
       ActionMailer::Base.default_url_options[:host] = 'test.blankapp.com'
     end
+
+    def stub_open_id(success, message, url = 'http://jamesgolick.com')
+      @controller.stubs(:authenticate_with_open_id).yields(stub(:successful? =&gt; success, :message =&gt; message), url)
+    end
 end</diff>
      <filename>test/test_helper.rb</filename>
    </modified>
    <modified>
      <diff>@@ -5,12 +5,12 @@ class PersonTest &lt; ActiveSupport::TestCase
     should &quot;set remember_token&quot; do
       assert_not_nil @person.remember_token      
     end
-    
+
     should &quot;set remember_token_expires_at&quot; do
       assert_not_nil @person.remember_token_expires_at      
     end
   end
-  
+
   context &quot;With a new person&quot; do
     setup do
       @person = Person.new
@@ -19,6 +19,14 @@ class PersonTest &lt; ActiveSupport::TestCase
     should_validate_presence_of :password, :password_confirmation
   end
 
+  context &quot;Given a person using OpenID&quot; do
+    setup do
+      @person = Factory(:person_with_open_id)
+    end
+
+    should_validate_uniqueness_of :open_id_url
+  end
+
   context &quot;Given an existing person&quot; do
     setup do
       @person = Factory(:person)
@@ -154,4 +162,20 @@ class PersonTest &lt; ActiveSupport::TestCase
       end
     end
   end
+
+  context &quot;An unauthenticated person using OpenID&quot; do
+    setup do
+      @person = Person.new(:open_id_url =&gt; 'http://some.openid.provider.net')
+      @person.open_id_url_message = &quot;you suck&quot;
+      @person.save
+    end
+
+    should &quot;NOT save&quot; do
+      assert @person.new_record?
+    end
+
+    should &quot;have errors on open_id_url matching the open_id_url_message&quot; do
+      assert_match /you suck/i, [@person.errors.on(:open_id_url)].flatten.to_sentence
+    end
+  end
 end</diff>
      <filename>test/unit/person_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>491e79a1658995ec13db34762430f2e8042851b6</id>
    </parent>
  </parents>
  <author>
    <name>Fran&#231;ois Beausoleil</name>
    <email>francois@teksol.info</email>
  </author>
  <url>http://github.com/giraffesoft/blank/commit/9308552c2536cfc5a9cbd4022baf284f6e859b79</url>
  <id>9308552c2536cfc5a9cbd4022baf284f6e859b79</id>
  <committed-date>2009-05-22T09:02:19-07:00</committed-date>
  <authored-date>2009-05-22T09:02:19-07:00</authored-date>
  <message>Added OpenID authentication &amp; registration</message>
  <tree>2bc345377a6365aa191e1bfc47a180f472cf9cb1</tree>
  <committer>
    <name>Fran&#231;ois Beausoleil</name>
    <email>francois@teksol.info</email>
  </committer>
</commit>
