pager-restful_open_id_authentication 1.0.20080507

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) hide show
  1. data/README +63 -0
  2. data/Rakefile +22 -0
  3. data/generators/open_id_authenticated/USAGE +1 -0
  4. data/generators/open_id_authenticated/open_id_authenticated_generator.rb +215 -0
  5. data/generators/open_id_authenticated/templates/activation.rhtml +3 -0
  6. data/generators/open_id_authenticated/templates/authenticated_system.rb +120 -0
  7. data/generators/open_id_authenticated/templates/authenticated_test_helper.rb +113 -0
  8. data/generators/open_id_authenticated/templates/controller.rb +94 -0
  9. data/generators/open_id_authenticated/templates/fixtures.yml +17 -0
  10. data/generators/open_id_authenticated/templates/functional_test.rb +85 -0
  11. data/generators/open_id_authenticated/templates/helper.rb +2 -0
  12. data/generators/open_id_authenticated/templates/login.rhtml +20 -0
  13. data/generators/open_id_authenticated/templates/migration.rb +45 -0
  14. data/generators/open_id_authenticated/templates/model.rb +93 -0
  15. data/generators/open_id_authenticated/templates/model_controller.rb +30 -0
  16. data/generators/open_id_authenticated/templates/model_functional_test.rb +72 -0
  17. data/generators/open_id_authenticated/templates/model_helper.rb +2 -0
  18. data/generators/open_id_authenticated/templates/notifier.rb +25 -0
  19. data/generators/open_id_authenticated/templates/notifier_test.rb +31 -0
  20. data/generators/open_id_authenticated/templates/observer.rb +11 -0
  21. data/generators/open_id_authenticated/templates/open_id_form.rhtml +14 -0
  22. data/generators/open_id_authenticated/templates/signup.rhtml +22 -0
  23. data/generators/open_id_authenticated/templates/signup_notification.rhtml +8 -0
  24. data/generators/open_id_authenticated/templates/unit_test.rb +101 -0
  25. data/install.rb +1 -0
  26. data/lib/controller_methods.rb +137 -0
  27. data/lib/open_id_store.rb +69 -0
  28. data/rails/init.rb +15 -0
  29. metadata +81 -0
@@ -0,0 +1,94 @@
1
+ # This controller handles the login/logout function of the site.
2
+ class <%= controller_class_name %>Controller < ApplicationController
3
+ open_id_consumer :required => [:email, :nickname]
4
+ # Be sure to include AuthenticationSystem in Application Controller instead
5
+ include AuthenticatedSystem
6
+ # If you want "remember me" functionality, add this before_filter to Application Controller
7
+ before_filter :login_from_cookie
8
+
9
+ # render new.rhtml
10
+ def new
11
+ @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>])
12
+ end
13
+
14
+ def create
15
+ self.current_<%= file_name %> = <%= class_name %>.authenticate(params[:login], params[:password])
16
+ if logged_in?
17
+ if params[:remember_me] == "1"
18
+ self.current_<%= file_name %>.remember_me
19
+ cookies[:auth_token] = { :value => self.current_<%= file_name %>.remember_token , :expires => self.current_<%= file_name %>.remember_token_expires_at }
20
+ end
21
+ redirect_back_or_default('/')
22
+ flash[:notice] = "Logged in successfully"
23
+ else
24
+ flash[:notice] = "Login failed"
25
+ render :action => 'new'
26
+ end
27
+ end
28
+
29
+ def destroy
30
+ self.current_<%= file_name %>.forget_me if logged_in?
31
+ cookies.delete :auth_token
32
+ reset_session
33
+ flash[:notice] = "You have been logged out."
34
+ redirect_back_or_default('/')
35
+ end
36
+
37
+ def begin
38
+ # If the URL was unusable (either because of network conditions, a server error,
39
+ # or that the response returned was not an OpenID identity page), the library
40
+ # will return HTTP_FAILURE or PARSE_ERROR. Let the user know that the URL is unusable.
41
+ case open_id_response.status
42
+ when OpenID::SUCCESS
43
+ # The URL was a valid identity URL. Now we just need to send a redirect
44
+ # to the server using the redirect_url the library created for us.
45
+
46
+ # redirect to the server
47
+ redirect_to open_id_response.redirect_url((request.protocol + request.host_with_port + "/"), complete_<%= controller_file_name %>_url)
48
+ else
49
+ flash[:error] = "Unable to find OpenID server for <q>#{params[:open_id_url]}</q>"
50
+ render :action => :new
51
+ end
52
+ end
53
+
54
+ def complete
55
+ case open_id_response.status
56
+ when OpenID::FAILURE
57
+ # In the case of failure, if info is non-nil, it is the URL that we were verifying.
58
+ # We include it in the error message to help the user figure out what happened.
59
+ flash[:notice] = if open_id_response.identity_url
60
+ "Verification of #{open_id_response.identity_url} failed. "
61
+ else
62
+ "Verification failed. "
63
+ end
64
+ flash[:notice] += open_id_response.msg.to_s
65
+ when OpenID::SUCCESS
66
+ # Success means that the transaction completed without error. If info is nil,
67
+ # it means that the user cancelled the verification.
68
+ flash[:notice] = "You have successfully verified #{open_id_response.identity_url} as your identity."
69
+ if open_id_fields.any?
70
+ @<%= file_name %> = <%= class_name %>.find_by_open_id_url(open_id_response.identity_url)
71
+ @<%= file_name %> ||= <%= class_name %>.new(:open_id_url => open_id_response.identity_url)
72
+ @<%= file_name %>.login = open_id_fields['nickname'] if open_id_fields['nickname']
73
+ @<%= file_name %>.email = open_id_fields['email'] if open_id_fields['email']
74
+ if @<%= file_name %>.save
75
+ self.current_<%= file_name %> = @<%= file_name %>
76
+ if params[:remember_me] == "1"
77
+ self.current_<%= file_name %>.remember_me
78
+ cookies[:auth_token] = { :value => self.current_<%= file_name %>.remember_token , :expires => self.current_<%= file_name %>.remember_token_expires_at }
79
+ end
80
+ flash[:notice] = "You have successfully verified #{open_id_response.identity_url} as your identity."
81
+ return redirect_back_or_default('/')
82
+ else
83
+ flash[:notice] = @<%= file_name %>.errors.full_messages.join('<br />')
84
+ render :action => 'new' and return
85
+ end
86
+ end
87
+ when OpenID::CANCEL
88
+ flash[:notice] = "Verification cancelled."
89
+ else
90
+ flash[:notice] = "Unknown response status: #{open_id_response.status}"
91
+ end
92
+ redirect_to :action => 'new'
93
+ end
94
+ end
@@ -0,0 +1,17 @@
1
+ quentin:
2
+ id: 1
3
+ login: quentin
4
+ email: quentin@example.com
5
+ salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
6
+ crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
7
+ created_at: <%%= 5.days.ago.to_s :db %>
8
+ <% if options[:include_activation] %> activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9b <% end %>
9
+ <% if options[:include_activation] %> activated_at: <%%= 5.days.ago.to_s :db %> <% end %>
10
+ aaron:
11
+ id: 2
12
+ login: aaron
13
+ email: aaron@example.com
14
+ salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
15
+ crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
16
+ created_at: <%%= 1.days.ago.to_s :db %>
17
+ <% if options[:include_activation] %> activation_code: 8f24789ae988411ccf33ab0c30fe9106fab32e9a <% end %>
@@ -0,0 +1,85 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require '<%= controller_file_name %>_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class <%= controller_class_name %>Controller; def rescue_action(e) raise e end; end
6
+
7
+ class <%= controller_class_name %>ControllerTest < Test::Unit::TestCase
8
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
9
+ # Then, you can remove it from this and the units test.
10
+ include AuthenticatedTestHelper
11
+
12
+ fixtures :<%= table_name %>
13
+
14
+ def setup
15
+ @controller = <%= controller_class_name %>Controller.new
16
+ @request = ActionController::TestRequest.new
17
+ @response = ActionController::TestResponse.new
18
+ end
19
+
20
+ def test_should_login_and_redirect
21
+ post :create, :login => 'quentin', :password => 'test'
22
+ assert session[:<%= file_name %>]
23
+ assert_response :redirect
24
+ end
25
+
26
+ def test_should_fail_login_and_not_redirect
27
+ post :create, :login => 'quentin', :password => 'bad password'
28
+ assert_nil session[:<%= file_name %>]
29
+ assert_response :success
30
+ end
31
+
32
+ def test_should_logout
33
+ login_as :quentin
34
+ get :destroy
35
+ assert_nil session[:<%= file_name %>]
36
+ assert_response :redirect
37
+ end
38
+
39
+ def test_should_remember_me
40
+ post :create, :login => 'quentin', :password => 'test', :remember_me => "1"
41
+ assert_not_nil @response.cookies["auth_token"]
42
+ end
43
+
44
+ def test_should_not_remember_me
45
+ post :create, :login => 'quentin', :password => 'test', :remember_me => "0"
46
+ assert_nil @response.cookies["auth_token"]
47
+ end
48
+
49
+ def test_should_delete_token_on_logout
50
+ login_as :quentin
51
+ get :destroy
52
+ assert_equal @response.cookies["auth_token"], []
53
+ end
54
+
55
+ def test_should_login_with_cookie
56
+ <%= table_name %>(:quentin).remember_me
57
+ @request.cookies["auth_token"] = cookie_for(:quentin)
58
+ get :new
59
+ assert @controller.send(:logged_in?)
60
+ end
61
+
62
+ def test_should_fail_expired_cookie_login
63
+ <%= table_name %>(:quentin).remember_me
64
+ <%= table_name %>(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago
65
+ @request.cookies["auth_token"] = cookie_for(:quentin)
66
+ get :new
67
+ assert !@controller.send(:logged_in?)
68
+ end
69
+
70
+ def test_should_fail_cookie_login
71
+ <%= table_name %>(:quentin).remember_me
72
+ @request.cookies["auth_token"] = auth_token('invalid_auth_token')
73
+ get :new
74
+ assert !@controller.send(:logged_in?)
75
+ end
76
+
77
+ protected
78
+ def auth_token(token)
79
+ CGI::Cookie.new('name' => 'auth_token', 'value' => token)
80
+ end
81
+
82
+ def cookie_for(<%= file_name %>)
83
+ auth_token <%= table_name %>(<%= file_name %>).remember_token
84
+ end
85
+ end
@@ -0,0 +1,2 @@
1
+ module <%= controller_class_name %>Helper
2
+ end
@@ -0,0 +1,20 @@
1
+ <h2>Sign in with OpenID</h2>
2
+ <%%= render :partial => "<%= controller_file_name %>/open_id_form" %>
3
+
4
+ <p>If you don't already have an OpenID account you can easily create one at <%%= link_to 'MyOpenID', 'http://www.myopenid.com' -%>.</p>
5
+
6
+ <h2>Or use your login/password</h2>
7
+ <%% form_tag <%= controller_singular_name %>_path do -%>
8
+ <p><label for="login">Login</label><br/>
9
+ <%%= text_field_tag 'login' %></p>
10
+
11
+ <p><label for="password">Password</label><br/>
12
+ <%%= password_field_tag 'password' %></p>
13
+
14
+ <!-- Uncomment this if you want this functionality
15
+ <p><label for="remember_me">Remember me:</label>
16
+ <%%= check_box_tag 'remember_me' %></p>
17
+ -->
18
+
19
+ <p><%%= submit_tag 'Log in' %></p>
20
+ <%% end -%>
@@ -0,0 +1,45 @@
1
+ class <%= migration_name %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :<%= table_name %>, :force => true do |t|
4
+ t.column :login, :string
5
+ t.column :email, :string
6
+ t.column :crypted_password, :string, :limit => 40
7
+ t.column :salt, :string, :limit => 40
8
+ t.column :created_at, :datetime
9
+ t.column :updated_at, :datetime
10
+ t.column :remember_token, :string
11
+ t.column :remember_token_expires_at, :datetime
12
+ t.column :open_id_url, :string
13
+ <% if options[:include_activation] %>
14
+ t.column :activation_code, :string, :limit => 40
15
+ t.column :activated_at, :datetime<% end %>
16
+ end
17
+
18
+ create_table :open_id_associations, :force => true do |t|
19
+ t.column :server_url, :binary
20
+ t.column :handle, :string
21
+ t.column :secret, :binary
22
+ t.column :issued, :integer
23
+ t.column :lifetime, :integer
24
+ t.column :assoc_type, :string
25
+ end
26
+
27
+ create_table :open_id_nonces, :force => true do |t|
28
+ t.column :nonce, :string
29
+ t.column :created, :integer
30
+ end
31
+
32
+ create_table :open_id_settings, :force => true do |t|
33
+ t.column :setting, :string
34
+ t.column :value, :binary
35
+ end
36
+ end
37
+
38
+ def self.down
39
+ drop_table :<%= table_name %>
40
+ drop_table :open_id_associations
41
+ drop_table :open_id_nonces
42
+ drop_table :open_id_settings
43
+ end
44
+
45
+ end
@@ -0,0 +1,93 @@
1
+ require 'digest/sha1'
2
+ class <%= class_name %> < ActiveRecord::Base
3
+ # Virtual attribute for the unencrypted password
4
+ attr_accessor :password
5
+
6
+ validates_presence_of :login, :email
7
+ validates_presence_of :password, :if => :password_required?
8
+ validates_presence_of :password_confirmation, :if => :password_required?
9
+ validates_length_of :password, :within => 4..40, :if => :password_required?
10
+ validates_confirmation_of :password, :if => :password_required?
11
+ validates_length_of :login, :within => 3..40
12
+ validates_length_of :email, :within => 3..100
13
+ validates_uniqueness_of :login, :email, :case_sensitive => false
14
+ before_save :encrypt_password
15
+ <% if options[:include_activation] %> before_create :make_activation_code <% end %>
16
+ <% if options[:include_activation] %>
17
+ # Activates the user in the database.
18
+ def activate
19
+ @activated = true
20
+ self.attributes = {:activated_at => Time.now.utc, :activation_code => nil}
21
+ save(false)
22
+ end
23
+
24
+ def activated?
25
+ !! activation_code.nil?
26
+ end
27
+
28
+ # Returns true if the user has just been activated.
29
+ def recently_activated?
30
+ @activated
31
+ end <% end %>
32
+ # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
33
+ def self.authenticate(login, password)
34
+ u = <% if options[:include_activation] %>find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login]<% else %>find_by_login(login)<% end %> # need to get the salt
35
+ u && u.authenticated?(password) ? u : nil
36
+ end
37
+
38
+ # Encrypts some data with the salt.
39
+ def self.encrypt(password, salt)
40
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
41
+ end
42
+
43
+ # Encrypts the password with the user salt
44
+ def encrypt(password)
45
+ self.class.encrypt(password, salt)
46
+ end
47
+
48
+ def authenticated?(password)
49
+ crypted_password == encrypt(password)
50
+ end
51
+
52
+ def remember_token?
53
+ remember_token_expires_at && Time.now.utc < remember_token_expires_at
54
+ end
55
+
56
+ # These create and unset the fields required for remembering users between browser closes
57
+ def remember_me
58
+ remember_me_for 2.weeks
59
+ end
60
+
61
+ def remember_me_for(time)
62
+ remember_me_until time.from_now.utc
63
+ end
64
+
65
+ def remember_me_until(time)
66
+ self.remember_token_expires_at = time
67
+ self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
68
+ save(false)
69
+ end
70
+
71
+ def forget_me
72
+ self.remember_token_expires_at = nil
73
+ self.remember_token = nil
74
+ save(false)
75
+ end
76
+
77
+ protected
78
+ # before filter
79
+ def encrypt_password
80
+ return if password.blank?
81
+ self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
82
+ self.crypted_password = encrypt(password)
83
+ end
84
+
85
+ def password_required?
86
+ open_id_url.nil? && (crypted_password.blank? || !password.blank?)
87
+ end
88
+
89
+ <% if options[:include_activation] %>
90
+ def make_activation_code
91
+ self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
92
+ end <% end %>
93
+ end
@@ -0,0 +1,30 @@
1
+ class <%= model_controller_class_name %>Controller < ApplicationController
2
+ # Be sure to include AuthenticationSystem in Application Controller instead
3
+ include AuthenticatedSystem
4
+ # If you want "remember me" functionality, add this before_filter to Application Controller
5
+ before_filter :login_from_cookie
6
+
7
+ # render new.rhtml
8
+ def new
9
+ end
10
+
11
+ def create
12
+ @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>])
13
+ @<%= file_name %>.save!
14
+ self.current_<%= file_name %> = @<%= file_name %>
15
+ redirect_back_or_default('/')
16
+ flash[:notice] = "Thanks for signing up!"
17
+ rescue ActiveRecord::RecordInvalid
18
+ render :action => 'new'
19
+ end
20
+ <% if options[:include_activation] %>
21
+ def activate
22
+ self.current_<%= file_name %> = <%= class_name %>.find_by_activation_code(params[:activation_code])
23
+ if logged_in? && !current_<%= file_name %>.activated?
24
+ current_<%= file_name %>.activate
25
+ flash[:notice] = "Signup complete!"
26
+ end
27
+ redirect_back_or_default('/')
28
+ end
29
+ <% end %>
30
+ end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/../test_helper'
2
+ require '<%= model_controller_file_name %>_controller'
3
+
4
+ # Re-raise errors caught by the controller.
5
+ class <%= model_controller_class_name %>Controller; def rescue_action(e) raise e end; end
6
+
7
+ class <%= model_controller_class_name %>ControllerTest < Test::Unit::TestCase
8
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
9
+ # Then, you can remove it from this and the units test.
10
+ include AuthenticatedTestHelper
11
+
12
+ fixtures :<%= table_name %>
13
+
14
+ def setup
15
+ @controller = <%= model_controller_class_name %>Controller.new
16
+ @request = ActionController::TestRequest.new
17
+ @response = ActionController::TestResponse.new
18
+ end
19
+
20
+ def test_should_allow_signup
21
+ assert_difference <%= class_name %>, :count do
22
+ create_<%= file_name %>
23
+ assert_response :redirect
24
+ end
25
+ end
26
+
27
+ def test_should_require_login_on_signup
28
+ assert_no_difference <%= class_name %>, :count do
29
+ create_<%= file_name %>(:login => nil)
30
+ assert assigns(:<%= file_name %>).errors.on(:login)
31
+ assert_response :success
32
+ end
33
+ end
34
+
35
+ def test_should_require_password_on_signup
36
+ assert_no_difference <%= class_name %>, :count do
37
+ create_<%= file_name %>(:password => nil)
38
+ assert assigns(:<%= file_name %>).errors.on(:password)
39
+ assert_response :success
40
+ end
41
+ end
42
+
43
+ def test_should_require_password_confirmation_on_signup
44
+ assert_no_difference <%= class_name %>, :count do
45
+ create_<%= file_name %>(:password_confirmation => nil)
46
+ assert assigns(:<%= file_name %>).errors.on(:password_confirmation)
47
+ assert_response :success
48
+ end
49
+ end
50
+
51
+ def test_should_require_email_on_signup
52
+ assert_no_difference <%= class_name %>, :count do
53
+ create_<%= file_name %>(:email => nil)
54
+ assert assigns(:<%= file_name %>).errors.on(:email)
55
+ assert_response :success
56
+ end
57
+ end
58
+ <% if options[:include_activation] %>
59
+ def test_should_activate_user
60
+ assert_nil <%= class_name %>.authenticate('aaron', 'test')
61
+ get :activate, :activation_code => <%= table_name %>(:aaron).activation_code
62
+ assert_redirected_to '/'
63
+ assert_not_nil flash[:notice]
64
+ assert_equal <%= table_name %>(:aaron), <%= class_name %>.authenticate('aaron', 'test')
65
+ end <% end %>
66
+
67
+ protected
68
+ def create_<%= file_name %>(options = {})
69
+ post :create, :<%= file_name %> => { :login => 'quire', :email => 'quire@example.com',
70
+ :password => 'quire', :password_confirmation => 'quire' }.merge(options)
71
+ end
72
+ end