authpwn_rails 0.9.6 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.travis.yml +6 -0
  2. data/Gemfile +3 -2
  3. data/Gemfile.lock +38 -36
  4. data/README.rdoc +6 -11
  5. data/VERSION +1 -1
  6. data/authpwn_rails.gemspec +30 -22
  7. data/lib/authpwn_rails.rb +2 -2
  8. data/lib/authpwn_rails/credential_model.rb +38 -0
  9. data/lib/authpwn_rails/credentials.rb +10 -0
  10. data/lib/authpwn_rails/credentials/email.rb +30 -0
  11. data/lib/authpwn_rails/credentials/facebook.rb +77 -0
  12. data/lib/authpwn_rails/credentials/password.rb +63 -0
  13. data/lib/authpwn_rails/engine.rb +5 -7
  14. data/lib/authpwn_rails/facebook_session.rb +5 -5
  15. data/lib/authpwn_rails/generators/{session_generator.rb → all_generator.rb} +28 -9
  16. data/lib/authpwn_rails/generators/templates/001_create_users.rb +3 -11
  17. data/lib/authpwn_rails/generators/templates/002_create_credentials.rb +19 -0
  18. data/lib/authpwn_rails/generators/templates/credential.rb +16 -0
  19. data/lib/authpwn_rails/generators/templates/credentials.yml +34 -0
  20. data/lib/authpwn_rails/generators/templates/session/forbidden.html.erb +2 -2
  21. data/lib/authpwn_rails/generators/templates/session/home.html.erb +1 -1
  22. data/lib/authpwn_rails/generators/templates/session/new.html.erb +6 -6
  23. data/lib/authpwn_rails/generators/templates/session_controller.rb +1 -1
  24. data/lib/authpwn_rails/generators/templates/session_controller_test.rb +2 -2
  25. data/lib/authpwn_rails/generators/templates/user.rb +2 -2
  26. data/lib/authpwn_rails/generators/templates/users.yml +5 -8
  27. data/lib/authpwn_rails/session.rb +7 -7
  28. data/lib/authpwn_rails/session_controller.rb +15 -13
  29. data/lib/authpwn_rails/test_extensions.rb +6 -6
  30. data/lib/authpwn_rails/user_model.rb +23 -92
  31. data/test/email_credential_test.rb +50 -0
  32. data/test/facebook_controller_test.rb +7 -2
  33. data/test/facebook_credential_test.rb +74 -0
  34. data/test/helpers/db_setup.rb +4 -4
  35. data/test/helpers/fbgraph.rb +6 -2
  36. data/test/password_credential_test.rb +67 -0
  37. data/test/session_controller_api_test.rb +12 -12
  38. data/test/test_helper.rb +1 -0
  39. data/test/user_test.rb +11 -100
  40. metadata +41 -25
  41. data/lib/authpwn_rails/facebook_token_model.rb +0 -66
  42. data/lib/authpwn_rails/generators/facebook_generator.rb +0 -18
  43. data/lib/authpwn_rails/generators/templates/002_create_facebook_tokens.rb +0 -15
  44. data/lib/authpwn_rails/generators/templates/facebook_token.rb +0 -6
  45. data/lib/authpwn_rails/generators/templates/facebook_tokens.yml +0 -10
  46. data/lib/authpwn_rails/generators/users_generator.rb +0 -16
  47. data/test/facebook_token_test.rb +0 -28
@@ -1,24 +1,24 @@
1
1
  # :nodoc: namespace
2
- module AuthpwnRails
2
+ module Authpwn
3
3
 
4
4
  # Included in test cases.
5
5
  module TestExtensions
6
6
  # Sets the authenticated user in the test session.
7
7
  def set_session_current_user(user)
8
- request.session[:current_user_pid] = user ? user.to_param : nil
8
+ request.session[:user_exuid] = user ? user.to_param : nil
9
9
  end
10
10
 
11
11
  # The authenticated user in the test session.
12
12
  def session_current_user
13
- return nil unless user_param = request.session[:current_user_pid]
13
+ return nil unless user_param = request.session[:user_exuid]
14
14
  User.find_by_param user_param
15
15
  end
16
- end # module AuthpwnRails::TestExtensions
16
+ end # module Authpwn::TestExtensions
17
17
 
18
- end # namespace AuthpwnRails
18
+ end # namespace Authpwn
19
19
 
20
20
 
21
21
  # :nodoc: extend Test::Unit
22
22
  class ActionController::TestCase
23
- include AuthpwnRails::TestExtensions
23
+ include Authpwn::TestExtensions
24
24
  end
@@ -2,119 +2,50 @@ require 'active_model'
2
2
  require 'active_support'
3
3
 
4
4
  # :nodoc: namespace
5
- module AuthpwnRails
5
+ module Authpwn
6
6
 
7
7
  # Included by the model class that represents users.
8
8
  #
9
- # Right now, some parts of the codebase assume the model will be named User.
9
+ # Parts of the codebase assume the model will be named User.
10
10
  module UserModel
11
11
  extend ActiveSupport::Concern
12
-
12
+
13
13
  included do
14
- # E-mail address identifying the user account.
15
- validates :email, :format => /^[A-Za-z0-9.+_]+@[^@]*\.(\w+)$/,
16
- :presence => true, :length => 1..128, :uniqueness => true
17
-
18
- # Hash of e-mail address of the user account.
19
- validates :email_hash, :length => 64..64, :allow_nil => false
20
-
21
- # Random string preventing dictionary attacks on the password database.
22
- validates :password_salt, :length => { :in => 1..16, :allow_nil => true }
14
+ # Externally-visible user ID.
15
+ #
16
+ # This is decoupled from "id" column to avoid leaking information about
17
+ # the application's usage.
18
+ validates :exuid, :presence => true, :length => 1..32, :uniqueness => true
23
19
 
24
- # SHA-256 of (salt + password).
25
- validates :password_hash, :length => { :in => 64..64, :allow_nil => true }
26
-
27
- # Virtual attribute: the user's password.
28
- attr_reader :password
29
- validates :password, :confirmation => true
30
-
31
- # Virtual attribute: confirmation for the user's password.
32
- attr_accessor :password_confirmation
33
- validates_confirmation_of :password
20
+ # Credentials used to authenticate the user.
21
+ has_many :credentials, :dependent => :destroy, :inverse_of => :user
34
22
 
35
- # Facebook token.
36
- has_one :facebook_token, :dependent => :destroy, :inverse_of => :user
23
+ before_validation :set_default_exuid, :on => :create
37
24
  end
38
25
 
39
- # Class methods on models that include AuthpwnRails::UserModel.
26
+ # Class methods on models that include Authpwn::UserModel.
40
27
  module ClassMethods
41
28
  # Queries the database using the value returned by User#to_param.
42
29
  #
43
30
  # Returns nil if no matching User exists.
44
31
  def find_by_param(param)
45
- where(:email_hash => param).first
46
- end
47
-
48
- # The authenticated user or nil.
49
- def find_by_email_and_password(email, password)
50
- user = where(:email => email).first
51
- (user && user.password_matches?(password)) ? user : nil
52
- end
53
-
54
- # Computes a password hash from a raw password and a salt.
55
- def hash_password(password, salt)
56
- Digest::SHA2.hexdigest(password + salt)
57
- end
58
-
59
- # Generates a random salt value.
60
- def random_salt
61
- [(0...12).map { |i| 1 + rand(255) }.pack('C*')].pack('m').strip
62
- end
63
-
64
- # Fills out a new user's information based on a Facebook access token.
65
- def create_with_facebook_token(token)
66
- self.create! :email => "#{token.external_uid}@graph.facebook.com"
32
+ where(:exuid => param).first
67
33
  end
68
-
69
- # The user that owns a given Facebook OAuth2 token.
70
- #
71
- # A new user will be created if the token doesn't belong to any user. This
72
- # is the case for a new visitor.
73
- def for_facebook_token(access_token)
74
- FacebookToken.for(access_token).user
75
- end
76
- end # module AuthpwnRails::UserModel::ClassMethods
34
+ end # module Authpwn::UserModel::ClassMethods
77
35
 
78
- # Included in models that include AuthpwnRails::UserModel.
36
+ # Included in models that include Authpwn::UserModel.
79
37
  module InstanceMethods
80
- # Resets the virtual password attributes.
81
- def reset_password
82
- @password = @password_confirmation = nil
83
- end
84
-
85
- # Compares the given password against the user's stored password.
86
- #
87
- # Returns +true+ for a match, +false+ otherwise.
88
- def password_matches?(passwd)
89
- password_hash == self.class.hash_password(passwd, password_salt)
90
- end
91
-
92
- # Password virtual attribute.
93
- def password=(new_password)
94
- @password = new_password
95
- self.password_salt = self.class.random_salt
96
- self.password_hash = new_password &&
97
- self.class.hash_password(new_password, password_salt)
98
- end
99
-
100
38
  # Use e-mails instead of exposing ActiveRecord IDs.
101
39
  def to_param
102
- email_hash
103
- end
104
-
105
- # :nodoc: overwrites
106
- def email=(new_email)
107
- super
108
- self.email_hash = new_email && Digest::SHA2.hexdigest(new_email)
40
+ exuid
109
41
  end
110
42
 
111
- # Do not expose password and ActiveRecord IDs in JSON representation.
112
- def as_json(options = {})
113
- options ||= {}
114
- super(options.merge(:except => [:password_salt, :password_hash, :id]))
43
+ # :nodoc: sets exuid to a (hopefully) unique value before validations occur.
44
+ def set_default_exuid
45
+ self.exuid ||= (Time.now.to_f * 1_000_000).to_i
115
46
  end
116
- end # module AuthpwnRails::UserModel::InstanceMethods
117
-
118
- end # namespace AuthpwnRails::UserModel
47
+ end # module Authpwn::UserModel::InstanceMethods
48
+
49
+ end # namespace Authpwn::UserModel
119
50
 
120
- end # namespace AuthpwnRails
51
+ end # namespace Authpwn
@@ -0,0 +1,50 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class EmailCredentialTest < ActiveSupport::TestCase
4
+ def setup
5
+ @credential = Credentials::Email.new :email => 'dvdjohn@mit.edu',
6
+ :user => users(:bill)
7
+ end
8
+
9
+ test 'setup' do
10
+ assert @credential.valid?
11
+ end
12
+
13
+ test 'verified required' do
14
+ @credential.verified = ''
15
+ assert !@credential.valid?
16
+ end
17
+
18
+ test 'user presence' do
19
+ @credential.user = nil
20
+ assert !@credential.valid?
21
+ end
22
+
23
+ test 'email presence' do
24
+ @credential.email = nil
25
+ assert !@credential.valid?
26
+ end
27
+
28
+ test 'email length' do
29
+ @credential.email = 'abcde' * 25 + '@mit.edu'
30
+ assert !@credential.valid?, 'Overly long email'
31
+ end
32
+
33
+ test 'email format' do
34
+ ['cos tan@gmail.com', 'costan@x@mit.edu'].each do |email|
35
+ @credential.email = email
36
+ assert !@credential.valid?, "Bad email format - #{email}"
37
+ end
38
+ end
39
+
40
+ test 'email uniqueness' do
41
+ @credential.email = credentials(:john_email).email
42
+ assert !@credential.valid?
43
+ end
44
+
45
+ test 'User#email_credential' do
46
+ assert_equal credentials(:john_email), users(:john).email_credential
47
+ assert_equal credentials(:jane_email), users(:jane).email_credential
48
+ assert_nil users(:bill).email_credential
49
+ end
50
+ end
@@ -28,7 +28,10 @@ class FacebookControllerTest < ActionController::TestCase
28
28
  end
29
29
 
30
30
  test "facebook token for existing user" do
31
- set_session_current_facebook_token facebook_tokens(:john).access_token
31
+ flexmock(Credentials::Facebook).should_receive(:uid_from_token).
32
+ with(credentials(:john_facebook).key).
33
+ and_return(credentials(:john_facebook).facebook_uid)
34
+ set_session_current_facebook_token credentials(:john_facebook).key
32
35
  get :show, {}
33
36
  assert_response :success
34
37
  assert_equal @user, assigns(:current_user)
@@ -36,8 +39,10 @@ class FacebookControllerTest < ActionController::TestCase
36
39
 
37
40
  test "new facebook token" do
38
41
  set_session_current_facebook_token @new_token
42
+ flexmock(Credentials::Facebook).should_receive(:uid_from_token).
43
+ with(@new_token).and_return('12345678')
39
44
  get :show, {}
40
45
  assert_response :success
41
- assert !(@user == assigns(:current_user))
46
+ assert_not_equal @user, assigns(:current_user)
42
47
  end
43
48
  end
@@ -0,0 +1,74 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class FacebookCredentialTest < ActiveSupport::TestCase
4
+ def setup
5
+ @code = 'AAAEj8jKX2a8BAA4kNheRhOs6SlECVcZCE9o5pPKMytOjjoiNAoZBGZAwuL4KrrxXWesfJRhzDZCJiqrcQG3UdjRRNtyMJQMZD'
6
+ @credential = Credentials::Facebook.new :facebook_uid => '1181310542',
7
+ :key => 'AAAEj8jKX2a8BAOBMZCjxBe4dw7cRoD1JVxUgZAtB6ozJlR4Viazh6OAYcHB5kZAtUwgjpDy7a54ZA1DObLmBT9X99CLWYOj5Stqx8bHwnE7EzyBS1WxY',
8
+ :user => users(:bill)
9
+ end
10
+
11
+ test 'setup' do
12
+ assert @credential.valid?
13
+ end
14
+
15
+ test 'key required' do
16
+ @credential.key = nil
17
+ assert !@credential.valid?
18
+ end
19
+
20
+ test 'user presence' do
21
+ @credential.user = nil
22
+ assert !@credential.valid?
23
+ end
24
+
25
+ test 'user uniqueness' do
26
+ @credential.user = users(:john)
27
+ assert !@credential.valid?
28
+ end
29
+
30
+ test 'facebook_uid uniqueness' do
31
+ @credential.facebook_uid = credentials(:jane_facebook).facebook_uid
32
+ assert !@credential.valid?
33
+ end
34
+
35
+ test "uid_from_token" do
36
+ assert_equal '1011950666', Credentials::Facebook.uid_from_token(@code)
37
+ end
38
+
39
+ test "for with existing access token" do
40
+ flexmock(Credentials::Facebook).should_receive(:uid_from_token).with(@code).
41
+ and_return(credentials(:jane_facebook).facebook_uid)
42
+
43
+ assert_equal credentials(:jane_facebook), Credentials::Facebook.for(@code),
44
+ 'Wrong token'
45
+ assert_equal @code, credentials(:jane_facebook).reload.key,
46
+ 'Token not refreshed'
47
+ end
48
+
49
+ test "for with new access token" do
50
+ credential = nil
51
+ flexmock(Credentials::Facebook).should_receive(:uid_from_token).
52
+ with(@credential.key).and_return('123456789')
53
+ assert_difference 'Credentials::Facebook.count', 1 do
54
+ credential = Credentials::Facebook.for @credential.key
55
+ end
56
+ assert_equal '123456789', credential.facebook_uid
57
+ assert_equal @credential.key, credential.key
58
+ assert !credential.new_record?, 'New credential not saved'
59
+ assert !credential.user.new_record?, "New credential's user not saved"
60
+ end
61
+
62
+ test 'User#facebook_credential' do
63
+ user = users(:john)
64
+ assert_equal credentials(:john_facebook), user.facebook_credential
65
+ end
66
+
67
+ test 'User#for_facebook_token' do
68
+ flexmock(Credentials::Facebook).should_receive(:uid_from_token).
69
+ with(credentials(:john_facebook).key).
70
+ and_return(credentials(:john_facebook).facebook_uid)
71
+ assert_equal users(:john),
72
+ User.for_facebook_token(credentials(:john_facebook).key)
73
+ end
74
+ end
@@ -4,12 +4,12 @@ ActiveRecord::Base.configurations = true
4
4
 
5
5
  ActiveRecord::Migration.verbose = false
6
6
  require 'authpwn_rails/generators/templates/001_create_users.rb'
7
- CreateUsers.up
8
- require 'authpwn_rails/generators/templates/002_create_facebook_tokens.rb'
9
- CreateFacebookTokens.up
7
+ CreateUsers.migrate :up
8
+ require 'authpwn_rails/generators/templates/002_create_credentials.rb'
9
+ CreateCredentials.migrate :up
10
10
 
11
- require 'authpwn_rails/generators/templates/facebook_token.rb'
12
11
  require 'authpwn_rails/generators/templates/user.rb'
12
+ require 'authpwn_rails/generators/templates/credential.rb'
13
13
 
14
14
  # :nodoc: open TestCase to setup fixtures
15
15
  class ActiveSupport::TestCase
@@ -1,6 +1,10 @@
1
1
  # :nodoc: stub FBGraphRails.config because it depends on Rails.root
2
2
  module FBGraphRails
3
3
  def self.config
4
- { 'id' => '12345', 'secret' => 'awesome', 'scope' => []}
4
+ {
5
+ 'id' => '320998114580911',
6
+ 'secret' => '7ded389d3c226e1f5d363b2df695be2f',
7
+ 'scope' => []
8
+ }
5
9
  end
6
- end
10
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ class PasswordCredentialTest < ActiveSupport::TestCase
4
+ def setup
5
+ @credential = Credentials::Password.new :password => 'awesome',
6
+ :password_confirmation => 'awesome', :user => users(:bill)
7
+ end
8
+
9
+ test 'setup' do
10
+ assert @credential.valid?
11
+ end
12
+
13
+ test 'key not required' do
14
+ @credential.key = nil
15
+ assert @credential.valid?
16
+ end
17
+
18
+ test 'user presence' do
19
+ @credential.user = nil
20
+ assert !@credential.valid?
21
+ end
22
+
23
+ test 'user uniqueness' do
24
+ @credential.user = users(:john)
25
+ assert !@credential.valid?
26
+ end
27
+
28
+ test 'password confirmation' do
29
+ @credential.password_confirmation = 'not awesome'
30
+ assert !@credential.valid?
31
+ end
32
+
33
+ test 'password required' do
34
+ @credential.password = @credential.password_confirmation = nil
35
+ assert !@credential.valid?
36
+ end
37
+
38
+ test 'authenticate' do
39
+ assert_equal true, @credential.authenticate('awesome')
40
+ assert_equal false, @credential.authenticate('not awesome'),
41
+ 'Bogus password'
42
+ assert_equal false, @credential.authenticate('password'),
43
+ "Another user's password"
44
+ end
45
+
46
+ test 'authenticate_email' do
47
+ assert_equal users(:john),
48
+ Credentials::Password.authenticate_email('john@gmail.com', 'password')
49
+ assert_equal nil,
50
+ Credentials::Password.authenticate_email('john@gmail.com', 'pa55w0rd'),
51
+ "Jane's password on John's account"
52
+ assert_equal users(:jane),
53
+ Credentials::Password.authenticate_email('jane@gmail.com', 'pa55w0rd')
54
+ assert_equal nil,
55
+ Credentials::Password.authenticate_email('jane@gmail.com', 'password'),
56
+ "John's password on Jane's account"
57
+ assert_equal nil,
58
+ Credentials::Password.authenticate_email('john@gmail.com', 'awesome'),
59
+ 'Bogus password'
60
+ end
61
+
62
+ test 'User#password_credential' do
63
+ assert_equal credentials(:john_password), users(:john).password_credential
64
+ assert_equal credentials(:jane_password), users(:jane).password_credential
65
+ assert_nil users(:bill).password_credential
66
+ end
67
+ end
@@ -11,6 +11,7 @@ class SessionControllerApiTest < ActionController::TestCase
11
11
 
12
12
  setup do
13
13
  @user = users(:john)
14
+ @email_credential = credentials(:john_email)
14
15
  end
15
16
 
16
17
  test "show renders welcome without a user" do
@@ -42,7 +43,7 @@ class SessionControllerApiTest < ActionController::TestCase
42
43
  get :show, :format => 'json'
43
44
  assert_response :ok
44
45
  data = ActiveSupport::JSON.decode response.body
45
- assert_equal @user.email, data['user']['email']
46
+ assert_equal @user.exuid, data['user']['exuid']
46
47
  assert_equal session[:_csrf_token], data['csrf']
47
48
  assert_equal @user, assigns(:user), 'home controller method not called'
48
49
  end
@@ -57,11 +58,10 @@ class SessionControllerApiTest < ActionController::TestCase
57
58
  get :new
58
59
  assert_template :new
59
60
  assert_nil assigns(:current_user), 'current_user should not be set'
60
- assert assigns(:user).new_record?, 'user instance variable should be fresh'
61
61
 
62
62
  assert_select 'form' do
63
- assert_select 'input#user_email'
64
- assert_select 'input#user_password'
63
+ assert_select 'input#email'
64
+ assert_select 'input#password'
65
65
  assert_select 'input[type=submit]'
66
66
  end
67
67
  end
@@ -77,18 +77,18 @@ class SessionControllerApiTest < ActionController::TestCase
77
77
  end
78
78
 
79
79
  test "create logs in with good account details" do
80
- post :create, :user => { :email => @user.email, :password => 'password' }
80
+ post :create, :email => @email_credential.email, :password => 'password'
81
81
  assert_redirected_to session_url
82
82
  assert_equal @user, assigns(:current_user), 'instance variable'
83
83
  assert_equal @user, session_current_user, 'session'
84
84
  end
85
85
 
86
86
  test "create by json logs in with good account details" do
87
- post :create, :user => { :email => @user.email, :password => 'password' },
87
+ post :create, :email => @email_credential.email, :password => 'password',
88
88
  :format => 'json'
89
89
  assert_response :ok
90
90
  data = ActiveSupport::JSON.decode response.body
91
- assert_equal @user.email, data['user']['email']
91
+ assert_equal @user.exuid, data['user']['exuid']
92
92
  assert_equal session[:_csrf_token], data['csrf']
93
93
  assert_equal @user, assigns(:current_user), 'instance variable'
94
94
  assert_equal @user, session_current_user, 'session'
@@ -96,13 +96,13 @@ class SessionControllerApiTest < ActionController::TestCase
96
96
 
97
97
  test "create redirects properly with good account details" do
98
98
  url = 'http://authpwn.redirect.url'
99
- post :create, :user => { :email => @user.email, :password => 'password' },
99
+ post :create, :email => @email_credential.email, :password => 'password',
100
100
  :redirect_url => url
101
101
  assert_redirected_to url
102
102
  end
103
103
 
104
104
  test "create does not log in with bad password" do
105
- post :create, :user => { :email => @user.email, :password => 'fail' }
105
+ post :create, :email => @email_credential.email, :password => 'fail'
106
106
  assert_redirected_to new_session_url
107
107
  assert_nil assigns(:current_user), 'instance variable'
108
108
  assert_nil session_current_user, 'session'
@@ -110,7 +110,7 @@ class SessionControllerApiTest < ActionController::TestCase
110
110
  end
111
111
 
112
112
  test "create by json does not log in with bad password" do
113
- post :create, :user => { :email => @user.email, :password => 'fail' },
113
+ post :create, :email => @email_credential.email, :password => 'fail',
114
114
  :format => 'json'
115
115
  assert_response :ok
116
116
  data = ActiveSupport::JSON.decode response.body
@@ -121,7 +121,7 @@ class SessionControllerApiTest < ActionController::TestCase
121
121
 
122
122
  test "create maintains redirect_url for bad logins" do
123
123
  url = 'http://authpwn.redirect.url'
124
- post :create, :user => { :email => @user.email, :password => 'fail' },
124
+ post :create, :email => @email_credential.email, :password => 'fail',
125
125
  :redirect_url => url
126
126
  assert_redirected_to new_session_url
127
127
  assert_not_nil flash[:notice]
@@ -129,7 +129,7 @@ class SessionControllerApiTest < ActionController::TestCase
129
129
  end
130
130
 
131
131
  test "create does not log in with bad e-mail" do
132
- post :create, :user => { :email => 'nobody@gmail.com', :password => 'no' }
132
+ post :create, :email => 'nobody@gmail.com', :password => 'no'
133
133
  assert_redirected_to new_session_url
134
134
  assert_nil assigns(:current_user), 'instance variable'
135
135
  assert_nil session_current_user, 'session'