From 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Wed, 16 Nov 2011 23:30:43 -0700 Subject: [PATCH 01/11] Adding Token-based authentication. --- lib/letmein.rb | 44 ++++++++++++++++----- test/letmein_test.rb | 91 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/lib/letmein.rb b/lib/letmein.rb index 09c4dfc..2d6d49d 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -1,4 +1,5 @@ require 'active_record' +require 'active_support/secure_random' require 'bcrypt' module LetMeIn @@ -17,13 +18,15 @@ class Railtie < Rails::Railtie # conf.identifier = 'username' # end class Config - ACCESSORS = %w(models attributes passwords salts) + ACCESSORS = %w(models attributes passwords salts tokens generate_tokens) attr_accessor *ACCESSORS def initialize - @models = ['User'] - @attributes = ['email'] - @passwords = ['password_hash'] - @salts = ['password_salt'] + @models = ['User'] + @attributes = ['email'] + @passwords = ['password_hash'] + @salts = ['password_salt'] + @tokens = ['auth_token'] + @generate_tokens = [false] end ACCESSORS.each do |a| define_method("#{a.singularize}=") do |val| @@ -48,6 +51,7 @@ class << self attr_accessor :login, # test@test.test :password, # secretpassword + :token, # 40 char, hex, single token authentication :object # authenticated object validate :authenticate @@ -59,6 +63,8 @@ def initialize(params = { }) self.class.attribute ||= LetMeIn.accessor(:attribute, LetMeIn.config.models.index(self.class.model)) self.login = params[:login] || params[self.class.attribute.to_sym] self.password = params[:password] + i = LetMeIn.config.models.index(self.class.model) + self.token = params[LetMeIn.accessor(:token, i).to_sym] if LetMeIn.config.generate_tokens[i] end def save @@ -87,11 +93,17 @@ def method_missing(method_name, *args) end def authenticate - p = LetMeIn.accessor(:password, LetMeIn.config.models.index(self.class.model)) - s = LetMeIn.accessor(:salt, LetMeIn.config.models.index(self.class.model)) + unless self.token + p = LetMeIn.accessor(:password, LetMeIn.config.models.index(self.class.model)) + s = LetMeIn.accessor(:salt, LetMeIn.config.models.index(self.class.model)) + + object = self.class.model.constantize.where(self.class.attribute => self.login).first + self.object = object if object && !object.send(p).blank? && object.send(p) == BCrypt::Engine.hash_secret(self.password, object.send(s)) + else + self.object = self.class.model.constantize.where(LetMeIn.accessor(:token, LetMeIn.config.models.index(self.class.model)) => self.token).first + end - object = self.class.model.constantize.where("#{self.class.attribute}" => self.login).first - self.object = if object && !object.send(p).blank? && object.send(p) == BCrypt::Engine.hash_secret(self.password, object.send(s)) + if self.object object else errors.add :base, 'Failed to authenticate' @@ -109,6 +121,7 @@ def self.included(base) base.instance_eval do attr_accessor :password before_save :encrypt_password + before_save :generate_token define_method :encrypt_password do if password.present? @@ -118,6 +131,19 @@ def self.included(base) self.send("#{p}=", BCrypt::Engine.hash_secret(password, self.send(s))) end end + + define_method :generate_token do + i = LetMeIn.config.models.index(self.class.to_s) + if LetMeIn.config.generate_tokens[i] + t = LetMeIn.accessor(:token, i) + token = nil + loop do + token = SecureRandom.hex(20) + break token unless base.where(t => token).exists? + end + self.send("#{t}=", token) + end + end end end end diff --git a/test/letmein_test.rb b/test/letmein_test.rb index 463ef7e..07d6e34 100644 --- a/test/letmein_test.rb +++ b/test/letmein_test.rb @@ -40,11 +40,13 @@ def setup t.column :email, :string t.column :password_hash, :string t.column :password_salt, :string + t.column :auth_token, :string end create_table :admins do |t| t.column :username, :string t.column :pass_hash, :string t.column :pass_salt, :string + t.column :token_auth, :string end end init_default_configuration @@ -53,10 +55,12 @@ def setup def init_default_configuration remove_session_classes LetMeIn.configure do |c| - c.models = ['User'] - c.attributes = ['email'] - c.passwords = ['password_hash'] - c.salts = ['password_salt'] + c.models = ['User'] + c.attributes = ['email'] + c.passwords = ['password_hash'] + c.salts = ['password_salt'] + c.tokens = ['auth_token'] + c.generate_tokens = [false] end LetMeIn.initialize end @@ -64,14 +68,16 @@ def init_default_configuration def init_custom_configuration remove_session_classes LetMeIn.configure do |c| - c.models = ['User', 'Admin'] - c.attributes = ['email', 'username'] - c.passwords = ['password_hash', 'pass_hash'] - c.salts = ['password_salt', 'pass_salt'] + c.models = ['User', 'Admin'] + c.attributes = ['email', 'username'] + c.passwords = ['password_hash', 'pass_hash'] + c.salts = ['password_salt', 'pass_salt'] + c.tokens = ['auth_token', 'token_auth'] + c.generate_tokens = [false, true] end LetMeIn.initialize end - + def remove_session_classes Object.send(:remove_const, :UserSession) rescue nil Object.send(:remove_const, :AdminSession) rescue nil @@ -90,6 +96,7 @@ def test_default_configuration_initialization assert_equal ['email'], LetMeIn.config.attributes assert_equal ['password_hash'], LetMeIn.config.passwords assert_equal ['password_salt'], LetMeIn.config.salts + assert_equal ['auth_token'], LetMeIn.config.tokens end def test_custom_configuration_initialization @@ -98,11 +105,13 @@ def test_custom_configuration_initialization c.attribute = 'username' c.password = 'encrypted_pass' c.salt = 'salt' + c.token = 'token_auth' end assert_equal ['Account'], LetMeIn.config.models assert_equal ['username'], LetMeIn.config.attributes assert_equal ['encrypted_pass'], LetMeIn.config.passwords assert_equal ['salt'], LetMeIn.config.salts + assert_equal ['token_auth'], LetMeIn.config.tokens end def test_model_integration @@ -223,4 +232,68 @@ def test_custom_admin_session assert session.valid? assert_equal admin, session.admin end + + # Token Authentication Related + + def test_generate_token_false_by_default + init_default_configuration + user = User.create!(:email => 'test@test.test', :password => 'pass') + assert_equal [false], LetMeIn.config.generate_tokens + assert_nil user.auth_token + end + + def test_generate_token + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + assert_match /^.{40}$/, admin.token_auth + end + + def test_token_session_init + init_custom_configuration + session = AdminSession.new(:token_auth => "29dkd38kduhuf88wldke21") + assert_equal "29dkd38kduhuf88wldke21", session.token + assert_nil session.object + assert_nil session.admin + end + + def test_ignore_token_session_init + init_default_configuration + session = UserSession.new(:auth_token => "29dkd38kduhuf88wldke21") + assert_nil session.token + assert_nil session.object + assert_nil session.user + end + + def test_token_authentication + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.create(:token_auth => admin.token_auth) + assert session.errors.blank? + assert_equal admin, session.object + assert_equal admin, session.admin + end + + def test_token_authentication_failure + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.create(:token_auth => 'bad_token') + assert session.errors.present? + assert_equal 'Failed to authenticate', session.errors[:base].first + assert_nil session.object + assert_nil session.admin + end + + def test_token_authentication_exception + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.new(:token_auth => 'bad_token') + begin + session.save! + rescue LetMeIn::Error => e + assert_equal 'Failed to authenticate', e.to_s + end + assert_nil session.object + assert_nil session.admin + end + end \ No newline at end of file From dbe5d2a02dee1858e22520a9dd4422cf6abc78bf Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Wed, 16 Nov 2011 23:30:43 -0700 Subject: [PATCH 02/11] Adding Token-based authentication. --- lib/letmein.rb | 44 ++++++++++++++++----- test/letmein_test.rb | 91 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/lib/letmein.rb b/lib/letmein.rb index 09c4dfc..2d6d49d 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -1,4 +1,5 @@ require 'active_record' +require 'active_support/secure_random' require 'bcrypt' module LetMeIn @@ -17,13 +18,15 @@ class Railtie < Rails::Railtie # conf.identifier = 'username' # end class Config - ACCESSORS = %w(models attributes passwords salts) + ACCESSORS = %w(models attributes passwords salts tokens generate_tokens) attr_accessor *ACCESSORS def initialize - @models = ['User'] - @attributes = ['email'] - @passwords = ['password_hash'] - @salts = ['password_salt'] + @models = ['User'] + @attributes = ['email'] + @passwords = ['password_hash'] + @salts = ['password_salt'] + @tokens = ['auth_token'] + @generate_tokens = [false] end ACCESSORS.each do |a| define_method("#{a.singularize}=") do |val| @@ -48,6 +51,7 @@ class << self attr_accessor :login, # test@test.test :password, # secretpassword + :token, # 40 char, hex, single token authentication :object # authenticated object validate :authenticate @@ -59,6 +63,8 @@ def initialize(params = { }) self.class.attribute ||= LetMeIn.accessor(:attribute, LetMeIn.config.models.index(self.class.model)) self.login = params[:login] || params[self.class.attribute.to_sym] self.password = params[:password] + i = LetMeIn.config.models.index(self.class.model) + self.token = params[LetMeIn.accessor(:token, i).to_sym] if LetMeIn.config.generate_tokens[i] end def save @@ -87,11 +93,17 @@ def method_missing(method_name, *args) end def authenticate - p = LetMeIn.accessor(:password, LetMeIn.config.models.index(self.class.model)) - s = LetMeIn.accessor(:salt, LetMeIn.config.models.index(self.class.model)) + unless self.token + p = LetMeIn.accessor(:password, LetMeIn.config.models.index(self.class.model)) + s = LetMeIn.accessor(:salt, LetMeIn.config.models.index(self.class.model)) + + object = self.class.model.constantize.where(self.class.attribute => self.login).first + self.object = object if object && !object.send(p).blank? && object.send(p) == BCrypt::Engine.hash_secret(self.password, object.send(s)) + else + self.object = self.class.model.constantize.where(LetMeIn.accessor(:token, LetMeIn.config.models.index(self.class.model)) => self.token).first + end - object = self.class.model.constantize.where("#{self.class.attribute}" => self.login).first - self.object = if object && !object.send(p).blank? && object.send(p) == BCrypt::Engine.hash_secret(self.password, object.send(s)) + if self.object object else errors.add :base, 'Failed to authenticate' @@ -109,6 +121,7 @@ def self.included(base) base.instance_eval do attr_accessor :password before_save :encrypt_password + before_save :generate_token define_method :encrypt_password do if password.present? @@ -118,6 +131,19 @@ def self.included(base) self.send("#{p}=", BCrypt::Engine.hash_secret(password, self.send(s))) end end + + define_method :generate_token do + i = LetMeIn.config.models.index(self.class.to_s) + if LetMeIn.config.generate_tokens[i] + t = LetMeIn.accessor(:token, i) + token = nil + loop do + token = SecureRandom.hex(20) + break token unless base.where(t => token).exists? + end + self.send("#{t}=", token) + end + end end end end diff --git a/test/letmein_test.rb b/test/letmein_test.rb index 463ef7e..07d6e34 100644 --- a/test/letmein_test.rb +++ b/test/letmein_test.rb @@ -40,11 +40,13 @@ def setup t.column :email, :string t.column :password_hash, :string t.column :password_salt, :string + t.column :auth_token, :string end create_table :admins do |t| t.column :username, :string t.column :pass_hash, :string t.column :pass_salt, :string + t.column :token_auth, :string end end init_default_configuration @@ -53,10 +55,12 @@ def setup def init_default_configuration remove_session_classes LetMeIn.configure do |c| - c.models = ['User'] - c.attributes = ['email'] - c.passwords = ['password_hash'] - c.salts = ['password_salt'] + c.models = ['User'] + c.attributes = ['email'] + c.passwords = ['password_hash'] + c.salts = ['password_salt'] + c.tokens = ['auth_token'] + c.generate_tokens = [false] end LetMeIn.initialize end @@ -64,14 +68,16 @@ def init_default_configuration def init_custom_configuration remove_session_classes LetMeIn.configure do |c| - c.models = ['User', 'Admin'] - c.attributes = ['email', 'username'] - c.passwords = ['password_hash', 'pass_hash'] - c.salts = ['password_salt', 'pass_salt'] + c.models = ['User', 'Admin'] + c.attributes = ['email', 'username'] + c.passwords = ['password_hash', 'pass_hash'] + c.salts = ['password_salt', 'pass_salt'] + c.tokens = ['auth_token', 'token_auth'] + c.generate_tokens = [false, true] end LetMeIn.initialize end - + def remove_session_classes Object.send(:remove_const, :UserSession) rescue nil Object.send(:remove_const, :AdminSession) rescue nil @@ -90,6 +96,7 @@ def test_default_configuration_initialization assert_equal ['email'], LetMeIn.config.attributes assert_equal ['password_hash'], LetMeIn.config.passwords assert_equal ['password_salt'], LetMeIn.config.salts + assert_equal ['auth_token'], LetMeIn.config.tokens end def test_custom_configuration_initialization @@ -98,11 +105,13 @@ def test_custom_configuration_initialization c.attribute = 'username' c.password = 'encrypted_pass' c.salt = 'salt' + c.token = 'token_auth' end assert_equal ['Account'], LetMeIn.config.models assert_equal ['username'], LetMeIn.config.attributes assert_equal ['encrypted_pass'], LetMeIn.config.passwords assert_equal ['salt'], LetMeIn.config.salts + assert_equal ['token_auth'], LetMeIn.config.tokens end def test_model_integration @@ -223,4 +232,68 @@ def test_custom_admin_session assert session.valid? assert_equal admin, session.admin end + + # Token Authentication Related + + def test_generate_token_false_by_default + init_default_configuration + user = User.create!(:email => 'test@test.test', :password => 'pass') + assert_equal [false], LetMeIn.config.generate_tokens + assert_nil user.auth_token + end + + def test_generate_token + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + assert_match /^.{40}$/, admin.token_auth + end + + def test_token_session_init + init_custom_configuration + session = AdminSession.new(:token_auth => "29dkd38kduhuf88wldke21") + assert_equal "29dkd38kduhuf88wldke21", session.token + assert_nil session.object + assert_nil session.admin + end + + def test_ignore_token_session_init + init_default_configuration + session = UserSession.new(:auth_token => "29dkd38kduhuf88wldke21") + assert_nil session.token + assert_nil session.object + assert_nil session.user + end + + def test_token_authentication + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.create(:token_auth => admin.token_auth) + assert session.errors.blank? + assert_equal admin, session.object + assert_equal admin, session.admin + end + + def test_token_authentication_failure + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.create(:token_auth => 'bad_token') + assert session.errors.present? + assert_equal 'Failed to authenticate', session.errors[:base].first + assert_nil session.object + assert_nil session.admin + end + + def test_token_authentication_exception + init_custom_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.new(:token_auth => 'bad_token') + begin + session.save! + rescue LetMeIn::Error => e + assert_equal 'Failed to authenticate', e.to_s + end + assert_nil session.object + assert_nil session.admin + end + end \ No newline at end of file From 2e2b9a57a74a64b8eec8ee524c1d51eba5317c7e Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Wed, 16 Nov 2011 23:58:45 -0700 Subject: [PATCH 03/11] More robust tests for tokens. --- test/letmein_test.rb | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/test/letmein_test.rb b/test/letmein_test.rb index 07d6e34..866287d 100644 --- a/test/letmein_test.rb +++ b/test/letmein_test.rb @@ -73,11 +73,24 @@ def init_custom_configuration c.passwords = ['password_hash', 'pass_hash'] c.salts = ['password_salt', 'pass_salt'] c.tokens = ['auth_token', 'token_auth'] - c.generate_tokens = [false, true] + c.generate_tokens = [false, false] end LetMeIn.initialize end + def init_token_configuration + remove_session_classes + LetMeIn.configure do |c| + c.models = ['User', 'Admin'] + c.attributes = ['email', 'username'] + c.passwords = ['password_hash', 'pass_hash'] + c.salts = ['password_salt', 'pass_salt'] + c.tokens = ['auth_token', 'token_auth'] + c.generate_tokens = [false, true] + end + LetMeIn.initialize + end + def remove_session_classes Object.send(:remove_const, :UserSession) rescue nil Object.send(:remove_const, :AdminSession) rescue nil @@ -243,13 +256,13 @@ def test_generate_token_false_by_default end def test_generate_token - init_custom_configuration + init_token_configuration admin = Admin.create!(:username => 'admin', :password => 'pass') assert_match /^.{40}$/, admin.token_auth end def test_token_session_init - init_custom_configuration + init_token_configuration session = AdminSession.new(:token_auth => "29dkd38kduhuf88wldke21") assert_equal "29dkd38kduhuf88wldke21", session.token assert_nil session.object @@ -265,7 +278,7 @@ def test_ignore_token_session_init end def test_token_authentication - init_custom_configuration + init_token_configuration admin = Admin.create!(:username => 'admin', :password => 'pass') session = AdminSession.create(:token_auth => admin.token_auth) assert session.errors.blank? @@ -274,7 +287,7 @@ def test_token_authentication end def test_token_authentication_failure - init_custom_configuration + init_token_configuration admin = Admin.create!(:username => 'admin', :password => 'pass') session = AdminSession.create(:token_auth => 'bad_token') assert session.errors.present? @@ -284,7 +297,7 @@ def test_token_authentication_failure end def test_token_authentication_exception - init_custom_configuration + init_token_configuration admin = Admin.create!(:username => 'admin', :password => 'pass') session = AdminSession.new(:token_auth => 'bad_token') begin From b6defad29b88470c369cb4b4a4873fa880ec63c2 Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Thu, 17 Nov 2011 00:01:04 -0700 Subject: [PATCH 04/11] updates --- test/letmein_test.rb | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/test/letmein_test.rb b/test/letmein_test.rb index 7a52da4..866287d 100644 --- a/test/letmein_test.rb +++ b/test/letmein_test.rb @@ -73,16 +73,11 @@ def init_custom_configuration c.passwords = ['password_hash', 'pass_hash'] c.salts = ['password_salt', 'pass_salt'] c.tokens = ['auth_token', 'token_auth'] -<<<<<<< HEAD c.generate_tokens = [false, false] -======= - c.generate_tokens = [false, true] ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 end LetMeIn.initialize end -<<<<<<< HEAD def init_token_configuration remove_session_classes LetMeIn.configure do |c| @@ -96,8 +91,6 @@ def init_token_configuration LetMeIn.initialize end -======= ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 def remove_session_classes Object.send(:remove_const, :UserSession) rescue nil Object.send(:remove_const, :AdminSession) rescue nil @@ -263,21 +256,13 @@ def test_generate_token_false_by_default end def test_generate_token -<<<<<<< HEAD init_token_configuration -======= - init_custom_configuration ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 admin = Admin.create!(:username => 'admin', :password => 'pass') assert_match /^.{40}$/, admin.token_auth end def test_token_session_init -<<<<<<< HEAD init_token_configuration -======= - init_custom_configuration ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 session = AdminSession.new(:token_auth => "29dkd38kduhuf88wldke21") assert_equal "29dkd38kduhuf88wldke21", session.token assert_nil session.object @@ -293,11 +278,7 @@ def test_ignore_token_session_init end def test_token_authentication -<<<<<<< HEAD init_token_configuration -======= - init_custom_configuration ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 admin = Admin.create!(:username => 'admin', :password => 'pass') session = AdminSession.create(:token_auth => admin.token_auth) assert session.errors.blank? @@ -306,11 +287,7 @@ def test_token_authentication end def test_token_authentication_failure -<<<<<<< HEAD init_token_configuration -======= - init_custom_configuration ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 admin = Admin.create!(:username => 'admin', :password => 'pass') session = AdminSession.create(:token_auth => 'bad_token') assert session.errors.present? @@ -320,11 +297,7 @@ def test_token_authentication_failure end def test_token_authentication_exception -<<<<<<< HEAD init_token_configuration -======= - init_custom_configuration ->>>>>>> 32e733d88f38f4aa6f47e20fa9cb98827e4864f4 admin = Admin.create!(:username => 'admin', :password => 'pass') session = AdminSession.new(:token_auth => 'bad_token') begin From 1f7048cc691179192a08816b59fb019b4158939d Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Thu, 17 Nov 2011 09:30:35 -0700 Subject: [PATCH 05/11] Adding brief documentation. --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index c52f914..8ec2da7 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,18 @@ Yes, you can do that too. Let's assume you also want to authenticate admins that Bam! You're done. Now you have an AdminSession object that will use *username* and *password* to authenticate. +Token-Based Authentication +========================== +Token authentication provides a simple solution for exposing APIs. When enabled, Letmein will automatically generate a token (40 character hex) for each new user. Instead of passing *email* and *password* to the *UserSession*, you merely pass *auth_token*. To enable tokens: + + LetMeIn.configure do |conf| + conf.generate_token = true + end + +Its usage differs from the *email/password* combo in only one way: + + @session = UserSession.new(params[:auth_token]) + Overriding Session Authentication ================================= By default user will be logged in if provided email and password match. If you need to add a bit more logic to that you'll need to create your own session object. In the following example we do an additional check to see if user is 'approved' before letting him in. From 424cbc299964440534e4b2042e2414a3a1d85493 Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Thu, 17 Nov 2011 09:35:32 -0700 Subject: [PATCH 06/11] Documentation edit. --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ec2da7..184e115 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,9 @@ Token authentication provides a simple solution for exposing APIs. When enabled, Its usage differs from the *email/password* combo in only one way: - @session = UserSession.new(params[:auth_token]) + @session = UserSession.new(:auth_token => "258082e5588dea110592154f48ef1e309a8bbff5") + +This will successfully (or not) authenticate you with the token above. Overriding Session Authentication ================================= From b1ec607818ba84ad9bef009c9bd71204d92995b8 Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Thu, 17 Nov 2011 11:24:30 -0700 Subject: [PATCH 07/11] Simplified. --- lib/letmein.rb | 17 ++++++++++++++--- test/letmein_test.rb | 8 ++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/lib/letmein.rb b/lib/letmein.rb index 2d6d49d..72c133a 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -63,8 +63,7 @@ def initialize(params = { }) self.class.attribute ||= LetMeIn.accessor(:attribute, LetMeIn.config.models.index(self.class.model)) self.login = params[:login] || params[self.class.attribute.to_sym] self.password = params[:password] - i = LetMeIn.config.models.index(self.class.model) - self.token = params[LetMeIn.accessor(:token, i).to_sym] if LetMeIn.config.generate_tokens[i] + self.token = params[self.token_attribute.to_sym] if allow_tokens? end def save @@ -100,10 +99,11 @@ def authenticate object = self.class.model.constantize.where(self.class.attribute => self.login).first self.object = object if object && !object.send(p).blank? && object.send(p) == BCrypt::Engine.hash_secret(self.password, object.send(s)) else - self.object = self.class.model.constantize.where(LetMeIn.accessor(:token, LetMeIn.config.models.index(self.class.model)) => self.token).first + self.object = self.class.model.constantize.where(self.token_attribute => self.token).first end if self.object + self.token = self.object.send(self.token_attribute) if allow_tokens? object else errors.add :base, 'Failed to authenticate' @@ -114,6 +114,17 @@ def authenticate def to_key nil end + + protected + + def token_attribute + LetMeIn.accessor(:token, LetMeIn.config.models.index(self.class.model)) + end + + def allow_tokens? + LetMeIn.config.generate_tokens[LetMeIn.config.models.index(self.class.model)] ? true : false + end + end module Model diff --git a/test/letmein_test.rb b/test/letmein_test.rb index 866287d..c66b2d9 100644 --- a/test/letmein_test.rb +++ b/test/letmein_test.rb @@ -277,6 +277,14 @@ def test_ignore_token_session_init assert_nil session.user end + def test_return_token_if_available + init_token_configuration + admin = Admin.create!(:username => 'admin', :password => 'pass') + session = AdminSession.create(:username => 'admin', :password => 'pass') + assert session.errors.blank? + assert_equal admin.token_auth, session.token + end + def test_token_authentication init_token_configuration admin = Admin.create!(:username => 'admin', :password => 'pass') From c8cccaa8ef8782d3018063c210db4bc771c58b5f Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Mon, 30 Jan 2012 22:52:09 -0700 Subject: [PATCH 08/11] Making compatible with Rails 3.2. --- lib/letmein.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/letmein.rb b/lib/letmein.rb index 72c133a..90dfc61 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -1,5 +1,5 @@ require 'active_record' -require 'active_support/secure_random' +require 'securerandom' require 'bcrypt' module LetMeIn From 5997d4d76e60ee16bc33e449aae6e6c2ec5c2c5d Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Tue, 13 Mar 2012 13:53:57 -0600 Subject: [PATCH 09/11] Fixed bug where it would make multiple tokens. --- lib/letmein.rb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/letmein.rb b/lib/letmein.rb index 90dfc61..cd8e3a9 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -145,14 +145,16 @@ def self.included(base) define_method :generate_token do i = LetMeIn.config.models.index(self.class.to_s) - if LetMeIn.config.generate_tokens[i] - t = LetMeIn.accessor(:token, i) - token = nil - loop do - token = SecureRandom.hex(20) - break token unless base.where(t => token).exists? + t = LetMeIn.accessor(:token, i) + unless self.send(t) + if LetMeIn.config.generate_tokens[i] + token = nil + loop do + token = SecureRandom.hex(20) + break token unless base.where(t => token).exists? + end + self.send("#{t}=", token) end - self.send("#{t}=", token) end end end @@ -184,4 +186,4 @@ def self.accessor(name, index = 0) Object.const_set(session_model, Class.new(LetMeIn::Session)) end end -end \ No newline at end of file +end From 35216ef41a139ccc86a72f581923ad63b6e2fe8f Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Tue, 13 Mar 2012 13:58:57 -0600 Subject: [PATCH 10/11] Slightly more effecient --- lib/letmein.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/letmein.rb b/lib/letmein.rb index cd8e3a9..7b1b2c6 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -145,9 +145,9 @@ def self.included(base) define_method :generate_token do i = LetMeIn.config.models.index(self.class.to_s) - t = LetMeIn.accessor(:token, i) - unless self.send(t) - if LetMeIn.config.generate_tokens[i] + if LetMeIn.config.generate_tokens[i] + t = LetMeIn.accessor(:token, i) + unless self.send(t).present? token = nil loop do token = SecureRandom.hex(20) From c08bc3658b2068b9a69a5ee5664ea1799405a962 Mon Sep 17 00:00:00 2001 From: Kendall Buchanan Date: Fri, 23 Mar 2012 17:14:07 -0600 Subject: [PATCH 11/11] Flags error if model has not been included in the initializer list (STI protection) --- Gemfile | 2 +- lib/letmein.rb | 6 +++++- test/letmein_test.rb | 12 +++++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index be46ff6..ad19037 100644 --- a/Gemfile +++ b/Gemfile @@ -10,4 +10,4 @@ end group :test do gem 'sqlite3' -end \ No newline at end of file +end diff --git a/lib/letmein.rb b/lib/letmein.rb index 09c4dfc..256dd3a 100644 --- a/lib/letmein.rb +++ b/lib/letmein.rb @@ -111,6 +111,10 @@ def self.included(base) before_save :encrypt_password define_method :encrypt_password do + unless LetMeIn.config.models.index(self.class.to_s) + raise(LetMeIn::Error, "#{self.class.to_s} must be added to your LetMeIn initializer") + end + if password.present? p = LetMeIn.accessor(:password, LetMeIn.config.models.index(self.class.to_s)) s = LetMeIn.accessor(:salt, LetMeIn.config.models.index(self.class.to_s)) @@ -147,4 +151,4 @@ def self.accessor(name, index = 0) Object.const_set(session_model, Class.new(LetMeIn::Session)) end end -end \ No newline at end of file +end diff --git a/test/letmein_test.rb b/test/letmein_test.rb index 463ef7e..9298d49 100644 --- a/test/letmein_test.rb +++ b/test/letmein_test.rb @@ -9,6 +9,7 @@ ActiveRecord::Base.logger = Logger.new($stdout) class User < ActiveRecord::Base ; end +class SubUser < User ; end class Admin < ActiveRecord::Base ; end class OpenSession < LetMeIn::Session @@ -223,4 +224,13 @@ def test_custom_admin_session assert session.valid? assert_equal admin, session.admin end -end \ No newline at end of file + + def test_throw_error_for_model_not_found + begin + user = SubUser.create!(:email => 'test@test.test', :password => 'pass') + rescue LetMeIn::Error => e + assert_equal 'SubUser must be added to your LetMeIn initializer', e.to_s + end + end + +end