Browse files

Released v0.10.0

  • Loading branch information...
1 parent ae1d3bb commit 35f14bafffbb64936e0c05dd7628872816248635 @binarylogic binarylogic committed Oct 27, 2008
View
8 CHANGELOG.rdoc
@@ -1,3 +1,11 @@
+== 0.10.0 released 2008-10-24
+
+* Do not allow instantiation if the session has not been activated with a controller object. Just like ActiveRecord won't let you do anything without a DB connection.
+* Abstracted controller implementation to allow for rails, merb, etc adapters. So this is not confined to the rails framework.
+* Removed create and update methods and added save, like ActiveRecord.
+* after_validation should be able to change the result if it adds errors on callbacks.
+* Completed tests.
+
== 0.9.1 released 2008-10-24
* Changed scope to id. Makes more sense to call it an id and fits better with the ActiveRecord model.
View
6 Manifest
@@ -1,7 +1,8 @@
CHANGELOG.rdoc
init.rb
lib/authgasm/acts_as_authentic.rb
-lib/authgasm/controller.rb
+lib/authgasm/controller_adapters/abstract_adapter.rb
+lib/authgasm/controller_adapters/rails_adapter.rb
lib/authgasm/session/active_record_trickery.rb
lib/authgasm/session/base.rb
lib/authgasm/session/callbacks.rb
@@ -77,6 +78,7 @@ test_app/script/server
test_app/test/fixtures/users.yml
test_app/test/functional/user_sessions_controller_test.rb
test_app/test/functional/users_controller_test.rb
+test_app/test/integration/user_sesion_stories_test.rb
+test_app/test/integration/user_session_test.rb
test_app/test/test_helper.rb
-test_app/test/unit/ass_test.rb
test_app/test/unit/user_test.rb
View
28 README.rdoc
@@ -19,7 +19,7 @@ What if your user sessions controller could look just like your other controller
def create
@user_session = UserSession.new(params[:user_session])
- if @user_session.create
+ if @user_session.save
redirect_to account_url
else
render :action => :new
@@ -134,6 +134,8 @@ Authgasm tries to check the state of the record before creating the session. If
What's neat about this is that these are checked upon any type of login. When logging in explicitly, by cookie, session, or basic http auth. So if you mark a user inactive in the middle of their session they wont be logged back in next time they refresh the page. Giving you complete control.
+Need Authgasm to check your own "state"? No problem, check out the hooks section below. Add in a before_validation or after_validation to do your own checking.
+
== Hooks / Callbacks
Just like ActiveRecord you can create your own hooks / callbacks so that you can do whatever you want when certain actions are performed. Here they are:
@@ -142,11 +144,27 @@ Just like ActiveRecord you can create your own hooks / callbacks so that you can
after_create
before_destroy
after_destroy
+ before_save
+ after_save
before_update
after_update
before_validation
after_validation
+== Errors
+
+The errors in Authgasm work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:
+
+ class UserSession
+ before_validation :check_if_awesome
+
+ private
+ def check_if_awesome
+ errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
+ errors.add_to_base("You must be awesome to log in") unless record.awesome?
+ end
+ end
+
== Automatic Session Updating
This is one of my favorite features that I think its pretty cool. It's things like this that make a library great and let you know you are on the right track.
@@ -183,7 +201,7 @@ When things come together like this I think its a sign that you are doing someth
You're asking: "why would I want multiple sessions?". Take this example:
-You have an app where users login and then need to re-login to view / change their billing information. Similar to how Apples' me.com works, if you've ever used it. What you could do is have the user login with their normal session, then have an entirely new session that represents their "secure" session. But wait, this is 2 users sessions. No problem:
+You have an app where users login and then need to re-login to view / change their billing information. Similar to how Apple's me.com works. What you could do is have the user login with their normal session, then have an entirely new session that represents their "secure" session. But wait, this is 2 users sessions. No problem:
# regular user session
@user_session = UserSession.new
@@ -202,9 +220,13 @@ This will keep everything separate. The :secure session will store its info in a
For more information on ids checkout Authgasm::Session::Base#initialize
+== What about [insert framework here]?
+
+As of now, authgasm supports rails right out of the box. But I designed authgasm to be framework agnostic. The only thing stopping Authgasm from being implemented in merb, or any other framework, is a simple adapter. I have not had the opportunity to use Authgasm in anything other than rails. If you want to use this in merb or any other framework take a look at authgasm/controller/rails_adapter.rb.
+
== How it works
-Interested in how all of this all works? Basically a before_filter is automatically set in your controller which lets Authgasm know about the current controller object. This allows Authgasm to set sessions, cookies, login via basic http auth, etc. If you are using rails in a multiple thread environment, don't worry. I kept that in mind and made this is thread safe.
+Interested in how all of this all works? Basically a before_filter is automatically set in your controller which lets Authgasm know about the current controller object. This allows Authgasm to set sessions, cookies, login via basic http auth, etc. If you are using rails in a multiple thread environment, don't worry. I kept that in mind and made this thread safe.
From there it is pretty simple. When you try to create a new session the record is authenticated and then all of the session / cookie magic is done for you. The sky is the limit.
View
1 init.rb
@@ -1,2 +1 @@
-require "digest/sha2"
require "authgasm"
View
5 lib/authgasm.rb
@@ -1,5 +1,8 @@
+require "digest/sha2"
require File.dirname(__FILE__) + "/authgasm/version"
-require File.dirname(__FILE__) + "/authgasm/controller"
+
+require File.dirname(__FILE__) + "/authgasm/controller_adapters/rails_adapter" if defined?(Rails)
+
require File.dirname(__FILE__) + "/authgasm/sha256_crypto_provider"
require File.dirname(__FILE__) + "/authgasm/acts_as_authentic"
require File.dirname(__FILE__) + "/authgasm/session/active_record_trickery"
View
38 lib/authgasm/acts_as_authentic.rb
@@ -1,5 +1,5 @@
module Authgasm
- module ActsAsAuthenticated # :nodoc:
+ module ActsAsAuthentic # :nodoc:
def self.included(base)
base.extend(ClassMethods)
end
@@ -20,6 +20,7 @@ module ClassMethods
# Class method name Description
# User.unique_token returns unique token generated by your :crypto_provider
# User.crypto_provider The class that you set in your :crypto_provider option
+ # User.forget_all! Resets all records so they will not be remembered on their next visit. Basically makes their cookies invalid
#
# Named Scopes
# User.logged_in Find all users who are logged in, based on your :logged_in_timeout option
@@ -31,6 +32,7 @@ module ClassMethods
# user.valid_password?(pass) Based on the valid of :password_field. Determines if the password passed is valid. The password could be encrypted or raw.
# user.randomize_password! Basically resets the password to a random password using only letters and numbers
# user.logged_in? Based on the :logged_in_timeout option. Tells you if the user is logged in or not
+ # user.forget! Changes their remember token, making their cookie invalid.
#
# === Options
# * <tt>session_class:</tt> default: "#{name}Session", the related session class. Used so that you don't have to repeat yourself here. A lot of the configuration will be based off of the configuration values of this class.
@@ -107,6 +109,17 @@ def self.unique_token
def self.crypto_provider
#{options[:crypto_provider]}
end
+
+ def self.forget_all!
+ # Paginate these to save on memory
+ records = nil
+ i = 0
+ begin
+ records = find(:all, :limit => 50, :offset => i)
+ records.each { |record| records.update_attribute(:#{options[:remember_token_field]}, unique_token) }
+ i += 50
+ end while !records.blank?
+ end
end_eval
# Instance methods
@@ -125,12 +138,13 @@ def #{options[:password_field]}=(pass)
return if pass.blank?
self.tried_to_set_#{options[:password_field]} = true
@#{options[:password_field]} = pass
- salt = [Array.new(6) {rand(256).chr}.join].pack("m").chomp
self.#{options[:remember_token_field]} = self.class.unique_token
- self.#{options[:password_salt_field]}, self.#{options[:crypted_password_field]} = salt, crypto_provider.encrypt(@#{options[:password_field]} + salt)
+ self.#{options[:password_salt_field]} = self.class.unique_token
+ self.#{options[:crypted_password_field]} = crypto_provider.encrypt(@#{options[:password_field]} + #{options[:password_salt_field]})
end
def valid_#{options[:password_field]}?(attempted_password)
+ return false if attempted_password.blank?
attempted_password == #{options[:crypted_password_field]} || #{options[:crypted_password_field]} == crypto_provider.encrypt(attempted_password + #{options[:password_salt_field]})
end
end_eval
@@ -145,6 +159,7 @@ def #{options[:password_field]}=(pass)
end
def valid_#{options[:password_field]}?(attemtped_password)
+ return false if attempted_password.blank?
attempted_password == #{options[:crypted_password_field]} || #{options[:crypted_password_field]} = crypto_provider.decrypt(attempted_password)
end
end_eval
@@ -158,6 +173,10 @@ def crypto_provider
self.class.crypto_provider
end
+ def forget!
+ update_attribute(:#{options[:remember_token_field]}, self.class.unique_token)
+ end
+
def randomize_#{options[:password_field]}!
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
newpass = ""
@@ -166,6 +185,13 @@ def randomize_#{options[:password_field]}!
self.confirm_#{options[:password_field]} = newpass
end
+ def save_from_session(*args)
+ @saving_from_session = true
+ result = save(*args)
+ @saving_from_session = false
+ result
+ end
+
protected
def create_sessions!
return if !#{options[:session_class]}.activated? || #{options[:session_ids].inspect}.blank?
@@ -183,7 +209,7 @@ def create_sessions!
end
def update_sessions!
- return if !#{options[:session_class]}.activated?
+ return if @saving_from_session || !#{options[:session_class]}.activated?
#{options[:session_ids].inspect}.each do |session_id|
session = #{options[:session_class]}.find(*[session_id].compact)
@@ -192,7 +218,7 @@ def update_sessions!
next if !session || session.record != self
# We know we are logged in and this is our record, update the session
- session.update
+ session.save
end
end
@@ -215,4 +241,4 @@ def validate_password
end
end
-ActiveRecord::Base.send(:include, Authgasm::ActsAsAuthenticated)
+ActiveRecord::Base.send(:include, Authgasm::ActsAsAuthentic)
View
16 lib/authgasm/controller.rb
@@ -1,16 +0,0 @@
-module Authgasm
- # = Controller
- # Adds a before_filter to set the controller object so that Authgasm can do its session and cookie magic
- module Controller
- def self.included(klass) # :nodoc:
- klass.prepend_before_filter :set_controller
- end
-
- private
- def set_controller
- Authgasm::Session::Base.controller = self
- end
- end
-end
-
-ActionController::Base.send(:include, Authgasm::Controller)
View
25 lib/authgasm/controller_adapters/abstract_adapter.rb
@@ -0,0 +1,25 @@
+module Authgasm
+ module ControllerAdapters # :nodoc:
+ # = Abstract Adapter
+ # Allows you to use Authgasm in any framework you want, not just rails. See tha RailsAdapter for an example of how to adapter Authgasm to work with your framework.
+ class AbstractAdapter
+ attr_accessor :controller
+
+ def initialize(controller)
+ self.controller = controller
+ end
+
+ def authenticate_with_http_basic(*args, &block)
+ end
+
+ def cookies
+ end
+
+ def request
+ end
+
+ def session
+ end
+ end
+ end
+end
View
39 lib/authgasm/controller_adapters/rails_adapter.rb
@@ -0,0 +1,39 @@
+module Authgasm
+ module ControllerAdapters
+ # = Rails Adapter
+ # Adapts authgasm to work with rails. The point is to close the gap between what authgasm expects and what the rails controller object
+ # provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
+ class RailsAdapter < AbstractAdapter
+ def authenticate_with_http_basic(*args, &block)
+ controller.authenticate_with_http_basic(*args, &block)
+ end
+
+ def cookies
+ controller.send(:cookies)
+ end
+
+ def request
+ controller.request
+ end
+
+ def session
+ controller.session
+ end
+ end
+
+ # = Rails Implementation
+ # Lets Authgasm know about the controller object, AKA "activates" authgasm.
+ module RailsImplementation
+ def self.included(klass) # :nodoc:
+ klass.prepend_before_filter :set_controller
+ end
+
+ private
+ def set_controller
+ Authgasm::Session::Base.controller = RailsAdapter.new(self)
+ end
+ end
+ end
+end
+
+ActionController::Base.send(:include, Authgasm::ControllerAdapters::RailsImplementation)
View
2 lib/authgasm/session/active_record_trickery.rb
@@ -18,7 +18,7 @@ def human_attribute_name(attribute_key_name, options = {})
module InstanceMethods # :nodoc:
def new_record?
- true
+ new_session?
end
end
end
View
239 lib/authgasm/session/base.rb
@@ -7,7 +7,9 @@ class Base
include Config
class << self
- # Returns true if a controller have been set and can be used properly.
+ # Returns true if a controller have been set and can be used properly. This MUST be set before anything can be done. Similar to how ActiveRecord won't allow you to do anything
+ # without establishing a DB connection. By default this is done for you automatically, but if you are using Authgasm in a unique way outside of rails, you need to assign a controller
+ # object to Authgasm via Authgasm::Session::Base.controller = obj.
def activated?
!controller.blank?
end
@@ -26,13 +28,13 @@ def controller # :nodoc:
# session.create
def create(*args)
session = new(*args)
- session.create
+ session.save
end
# Same as create but calls create!, which raises an exception when authentication fails
def create!(*args)
session = new(*args)
- session.create!
+ session.save!
end
# Finds your session by session, then cookie, and finally basic http auth. Perfect for that global before_filter to find your logged in user:
@@ -49,9 +51,13 @@ def find(id = nil)
args = [id].compact
session = new(*args)
find_with.each do |find_method|
- args = []
- args << true unless find_method == :session
- return session if session.send("valid_#{find_method}?", *args)
+ if session.send("valid_#{find_method}?")
+ if session.record.class.column_names.include?("last_request_at")
+ session.record.last_request_at = Time.now
+ session.record.save_from_session(false)
+ end
+ return session
+ end
end
nil
end
@@ -72,29 +78,15 @@ def klass_name # :nodoc:
end
end
- # Convenience method. The same as:
- #
- # session = UserSession.new
- # session.update
- def update(*args)
- session = new(*args)
- session.update
- end
-
- # The same as update but calls update!, which raises an exception when authentication fails
- def update!(*args)
- session = new(*args)
- session.update!
- end
-
private
def controllers
@@controllers ||= {}
end
end
- attr_accessor :login_with, :remember_me, :id
+ attr_accessor :login_with, :new_session, :remember_me
attr_reader :record, :unauthorized_record
+ attr_writer :id
# You can initialize a session by doing any of the following:
#
@@ -111,6 +103,8 @@ def controllers
# Ids are rarely used, but they can be useful. For example, what if users allow other users to login into their account via proxy? Now that user can "technically" be logged into 2 accounts at once.
# To solve this just pass a id called :proxy, or whatever you want. Authgasm will separate everything out.
def initialize(*args)
+ raise NotActivated.new(self) unless self.class.activated?
+
create_configurable_methods!
self.id = args.pop if args.last.is_a?(Symbol)
@@ -131,45 +125,6 @@ def initialize(*args)
end
end
- # Creates a new user session for you. It does all of the magic:
- #
- # 1. validates
- # 2. sets session
- # 3. sets cookie
- # 4. updates magic fields
- def create(updating = false)
- if valid?(true)
- cookies[cookie_key] = {
- :value => record.send(remember_token_field),
- :expires => remember_me? ? remember_me_for.from_now : nil
- }
-
- if !updating
- record.login_count = record.login_count + 1 if record.respond_to?(:login_count)
-
- if record.respond_to?(:current_login_at)
- record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
- record.current_login_at = Time.now
- end
-
- if record.respond_to?(:current_login_ip)
- record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
- record.current_login_ip = controller.request.remote_ip
- end
-
- record.save(false)
- end
-
- self
- end
- end
-
- # Same as create but raises an exception when authentication fails
- def create!(updating = false)
- raise SessionInvalid.new(self) unless create(updating)
- end
- alias_method :start!, :create!
-
# Your login credentials in hash format. Usually {:login => "my login", :password => "<protected>"} depending on your configuration.
# Password is protected as a security measure. The raw password should never be publicly accessible.
def credentials
@@ -178,69 +133,134 @@ def credentials
# Lets you set your loging and password via a hash format.
def credentials=(values)
- values.symbolize_keys!
- raise(ArgumentError, "Only 2 credentials are allowed: #{login_field} and #{password_field}") if !values.is_a?(Hash) || values.keys.size > 2 || !values.key?(login_field) || !values.key?(password_field)
+ return if values.blank? || !values.is_a?(Hash)
+ raise(ArgumentError, "Only 2 credentials are allowed: #{login_field} and #{password_field}") if (values.keys - [login_field.to_sym, login_field.to_s, password_field.to_sym, password_field.to_s]).size > 0
values.each { |field, value| send("#{field}=", value) }
end
# Resets everything, your errors, record, cookies, and session. Basically "logs out" a user.
def destroy
errors.clear
@record = nil
- cookies.delete cookie_key
- session[session_key] = nil
+ controller.cookies.delete cookie_key
+ controller.session[session_key] = nil
true
end
- # Errors when authentication fails, just like ActiveRecord errors. In fact it uses the same exact class.
+ # The errors in Authgasm work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:
+ #
+ # === Example
+ #
+ # class UserSession
+ # before_validation :check_if_awesome
+ #
+ # private
+ # def check_if_awesome
+ # errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
+ # errors.add_to_base("You must be awesome to log in") unless record.awesome?
+ # end
+ # end
def errors
@errors ||= Errors.new(self)
end
+ # Allows you to set a unique identifier for your session, so that you can have more than 1 session at a time. A good example when this might be needed is when you want to have a normal user session
+ # and a "secure" user session. The secure user session would be created only when they want to modify their billing information, or other sensative information. Similar to me.com. This requires 2
+ # user sessions. Just use an id for the "secure" session and you should be good.
+ #
+ # You can set the id a number of ways:
+ #
+ # session = Session.new(:secure)
+ # session = Session.new("username", "password", :secure)
+ # session = Session.new({:username => "username", :password => "password"}, :secure)
+ # session.id = :secure
+ #
+ # Just be sure and set your id before you validate / create / update your session.
+ def id
+ @id
+ end
+
def inspect # :nodoc:
details = {}
case login_with
when :unauthorized_record
- details[:unauthorized_record] = unauthorized_record
+ details[:unauthorized_record] = "<protected>"
else
details[login_field.to_sym] = send(login_field)
details[password_field.to_sym] = "<protected>"
end
"#<#{self.class.name} #{details.inspect}>"
end
+
+ def new_session?
+ new_session != false
+ end
+
# Allows users to be remembered via a cookie.
def remember_me?
- remember_me == true || remember_me = "true" || remember_me == "1"
+ remember_me == true || remember_me == "true" || remember_me == "1"
end
# When to expire the cookie. See remember_me_for configuration option to change this.
def remember_me_until
+ return unless remember_me?
remember_me_for.from_now
end
+ # Creates / updates a new user session for you. It does all of the magic:
+ #
+ # 1. validates
+ # 2. sets session
+ # 3. sets cookie
+ # 4. updates magic fields
+ def save
+ if valid?
+ update_session!
+ controller.cookies[cookie_key] = {
+ :value => record.send(remember_token_field),
+ :expires => remember_me_until
+ }
+
+ record.login_count = record.login_count + 1 if record.respond_to?(:login_count)
+
+ if record.respond_to?(:current_login_at)
+ record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
+ record.current_login_at = Time.now
+ end
+
+ if record.respond_to?(:current_login_ip)
+ record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
+ record.current_login_ip = controller.request.remote_ip
+ end
+
+ record.save_from_session(false)
+
+ self.new_session = false
+ self
+ end
+ end
+
+ # Same as save but raises an exception when authentication fails
+ def save!
+ result = save
+ raise SessionInvalid.new(self) unless result
+ result
+ end
+
# Sometimes you don't want to create a session via credentials (login and password). Maybe you already have the record. Just set this record to this and it will be authenticated when you try to validate
# the session. Basically this is another form of credentials, you are just skipping username and password validation.
def unauthorized_record=(value)
self.login_with = :unauthorized_record
@unauthorized_record = value
end
- # Updates the session with any new information. Resets the session and cookie.
- def update
- create(true)
- end
-
- # Same as update but raises an exception if validation is failed
- def update!
- create!(true)
- end
-
- def valid?(set_session = false)
+ def valid?
errors.clear
temp_record = unauthorized_record
- if login_with == :credentials
+ case login_with
+ when :credentials
errors.add(login_field, "can not be blank") if login.blank?
errors.add(password_field, "can not be blank") if protected_password.blank?
return false if errors.count > 0
@@ -256,6 +276,19 @@ def valid?(set_session = false)
errors.add(password_field, "is invalid")
return false
end
+ when :unauthorized_record
+ if temp_record.blank?
+ errors.add_to_base("You can not log in with a blank record.")
+ return false
+ end
+
+ if temp_record.new_record?
+ errors.add_to_base("You can not login with a new record.") if temp_record.new_record?
+ return false
+ end
+ else
+ errors.add_to_base("You must provide some form of credentials before logging in.")
+ return false
end
[:approved, :confirmed, :inactive].each do |required_status|
@@ -268,34 +301,34 @@ def valid?(set_session = false)
# All is good, lets set the record
@record = temp_record
- # Now lets set the session to make things easier on successive requests. This is nice when logging in from a cookie, the next requests will be right from the session, which is quicker.
- if set_session
- session[session_key] = record.id
- if record.class.column_names.include?("last_request_at")
- record.last_request_at = Time.now
- record.save(false)
- end
- end
-
true
end
- def valid_http_auth?(set_session = false)
+ def valid_http_auth?
controller.authenticate_with_http_basic do |login, password|
if !login.blank? && !password.blank?
send("#{login_method}=", login)
send("#{password_method}=", password)
- return valid?(set_session)
+ result = valid?
+ if result
+ update_session!
+ return result
+ end
end
end
false
end
- def valid_cookie?(set_session = false)
+ def valid_cookie?
if cookie_credentials
self.unauthorized_record = klass.send("find_by_#{remember_token_field}", cookie_credentials)
- valid?(set_session)
+ result = valid?
+ if result
+ update_session!
+ self.new_session = false
+ return result
+ end
end
false
@@ -304,7 +337,11 @@ def valid_cookie?(set_session = false)
def valid_session?
if session_credentials
self.unauthorized_record = klass.find_by_id(session_credentials)
- return valid?
+ result = valid?
+ if result
+ self.new_session = false
+ return result
+ end
end
false
@@ -315,12 +352,8 @@ def controller
self.class.controller
end
- def cookies
- controller.send(:cookies)
- end
-
def cookie_credentials
- cookies[cookie_key]
+ controller.cookies[cookie_key]
end
def create_configurable_methods!
@@ -356,12 +389,12 @@ def protected_password
@password
end
- def session
- controller.session
+ def session_credentials
+ controller.session[session_key]
end
- def session_credentials
- session[session_key]
+ def update_session!
+ controller.session[session_key] = record && record.id
end
end
end
View
40 lib/authgasm/session/callbacks.rb
@@ -4,23 +4,16 @@ module Session
#
# Just like in ActiveRecord you have before_save, before_validation, etc. You have similar callbacks with Authgasm, see all callbacks below.
module Callbacks
- CALLBACKS = %w(before_create after_create before_destroy after_destroy before_update after_update before_validation after_validation)
+ CALLBACKS = %w(before_create after_create before_destroy after_destroy before_save after_save before_update after_update before_validation after_validation)
def self.included(base) #:nodoc:
- [:create, :destroy, :update, :valid?].each do |method|
+ [:destroy, :save, :valid?].each do |method|
base.send :alias_method_chain, method, :callbacks
end
base.send :include, ActiveSupport::Callbacks
base.define_callbacks *CALLBACKS
end
-
- def create_with_callbacks(updating = false) # :nodoc:
- run_callbacks(:before_create)
- result = create_without_callbacks(updating)
- run_callbacks(:after_create) if result
- result
- end
def destroy_with_callbacks # :nodoc:
run_callbacks(:before_destroy)
@@ -29,17 +22,32 @@ def destroy_with_callbacks # :nodoc:
result
end
- def update_with_callbacks # :nodoc:
- run_callbacks(:before_update)
- result = update_without_callbacks
- run_callbacks(:after_update) if result
+ def save_with_callbacks # :nodoc:
+ if new_session?
+ run_callbacks(:before_create)
+ else
+ run_callbacks(:before_update)
+ end
+ run_callbacks(:before_save)
+ result = save_without_callbacks
+ if result
+ if new_session?
+ run_callbacks(:after_create)
+ else
+ run_callbacks(:after_update)
+ end
+ run_callbacks(:after_save)
+ end
result
end
- def valid_with_callbacks?(set_session = false) # :nodoc:
+ def valid_with_callbacks? # :nodoc:
run_callbacks(:before_validation)
- result = valid_without_callbacks?(set_session)
- run_callbacks(:after_validation) if result
+ result = valid_without_callbacks?
+ if result
+ run_callbacks(:after_validation)
+ result = errors.empty?
+ end
result
end
end
View
10 lib/authgasm/session/config.rb
@@ -22,15 +22,6 @@ def self.included(klass)
# # ... more configuration
# end
#
- # or...
- #
- # class UserSession < Authgasm::Session::Base
- # configure do |config|
- # config.authenticate_with = User
- # # ... more configuration
- # end
- # end
- #
# See the methods belows for all configuration options.
module ClassMethods
# Lets you change which model to use for authentication.
@@ -181,6 +172,7 @@ def password_field
end
def remember_me_for
+ return unless remember_me?
self.class.remember_me_for
end
View
6 lib/authgasm/session/errors.rb
@@ -3,6 +3,12 @@ module Session
class Errors < ::ActiveRecord::Errors # :nodoc:
end
+ class NotActivated < ::StandardError # :nodoc:
+ def initialize(session)
+ super("You must activate the Authgasm::Session::Base.controller with a controller object before creating objects")
+ end
+ end
+
class SessionInvalid < ::StandardError # :nodoc:
def initialize(session)
super("Authentication failed: #{session.errors.full_messages.to_sentence}")
View
4 lib/authgasm/version.rb
@@ -43,8 +43,8 @@ def to_a
end
MAJOR = 0
- MINOR = 9
- TINY = 1
+ MINOR = 10
+ TINY = 0
# The current version as a Version instance
CURRENT = new(MAJOR, MINOR, TINY)
View
2 test_app/app/controllers/user_sessions_controller.rb
@@ -9,7 +9,7 @@ def new
def create
@user_session = UserSession.new(params[:user_session])
- if @user_session.create
+ if @user_session.save
flash[:notice] = "Login successful!"
redirect_back_or_default(account_url)
else
View
BIN test_app/db/development.sqlite3
Binary file not shown.
View
BIN test_app/db/test.sqlite3
Binary file not shown.
View
4 test_app/test/fixtures/users.yml
@@ -1,6 +1,8 @@
ben:
id: 1
login: bjohnson
- crypted_password: <%= Authgasm::Sha256CryptoProvider.encrypt("benrocks") %>
+ password_salt: <%= salt = User.unique_token %>
+ crypted_password: <%= Authgasm::Sha256CryptoProvider.encrypt("benrocks" + salt) %>
+ remember_token: 23a1d7c66f456b14b45211aa656ce8ba7052fd220cd2d07a5c323792938f2a14
first_name: Ben
last_name: Johnson
View
27 test_app/test/functional/user_sessions_controller_test.rb
@@ -6,10 +6,31 @@ def setup
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
-
- def test_truth
+
+ def test_new
+ get :new
+ assert @controller.instance_variable_get(:@user_session).is_a?(UserSession)
+ end
+
+ def test_successful_create
get :create, {:user_session => {:login => "bjohnson", :password => "benrocks"}}
assert_equal 1, session[:user_id]
- assert_equal ["YmpvaG5zb24=\n:::2e8884187c71ff39af9ac05ebcaa0f40ab2432de51035aff8b0f491f890314d0"], cookies["user_credentials"]
+ assert_equal ["23a1d7c66f456b14b45211aa656ce8ba7052fd220cd2d07a5c323792938f2a14"], cookies["user_credentials"]
+ assert_redirected_to account_url
+ end
+
+ def test_unsuccessful_create
+ get :create, {:user_session => {:login => "bjohnson", :password => "badpassword"}}
+ assert_equal nil, session[:user_id]
+ assert_equal nil, cookies["user_credentials"]
+ assert_template "new"
+ end
+
+ def test_destroy
+ get :destroy
+ assert_equal nil, session[:user_id]
+ assert_equal nil, cookies["user_credentials"]
+ assert_redirected_to new_user_session_url
+ assert flash.key?(:notice)
end
end
View
85 test_app/test/integration/user_sesion_stories_test.rb
@@ -0,0 +1,85 @@
+require 'test_helper'
+
+class UserSessionStoriesTest < ActionController::IntegrationTest
+ def test_registration
+ # Try to access the account area without being logged in
+ get account_url
+ assert_redirected_to new_user_session_url
+ follow_redirect!
+ assert flash.key?(:notice)
+ assert_template "user_sessions/new"
+
+ # Try to register with no info
+ post users_url
+ assert_template "users/new"
+
+ # Register successfully
+ post users_url, {:user => {:login => "binarylogic", :password => "pass", :confirm_password => "pass", :first_name => "Ben", :last_name => "Johnson"}}
+ assert_redirected_to account_url
+ assert flash.key?(:notice)
+
+ access_account(User.find(2))
+ end
+
+ def test_login_process
+ # Try to access the account area without being logged in
+ get account_url
+ assert_redirected_to new_user_session_url
+ follow_redirect!
+ assert flash.key?(:notice)
+ assert_template "user_sessions/new"
+
+ login_unsuccessfully
+ login_unsuccessfully("bjohnson", "badpassword")
+ login_successfully("bjohnson", "benrocks")
+
+ # Try to log in again after a successful login
+ get new_user_session_url
+ assert_redirected_to account_url
+ follow_redirect!
+ assert flash.key?(:notice)
+ assert_template "users/show"
+
+ # Try to register after a successful login
+ get new_user_url
+ assert_redirected_to account_url
+ follow_redirect!
+ assert flash.key?(:notice)
+ assert_template "users/show"
+
+ access_account
+ logout(new_user_url) # before I tried to register, it stored my location
+
+ # Try to access my account again
+ get account_url
+ assert_redirected_to new_user_session_url
+ assert flash.key?(:notice)
+ end
+
+ def test_changing_password
+ # Try logging in with correct credentials
+ login_successfully("bjohnson", "benrocks")
+
+ # Go to edit form
+ get edit_account_path
+ assert_template "users/edit"
+
+ # Edit password
+ put account_path, :user => {:login => "bjohnson", :password => "sillywilly", :confirm_password => "sillywilly", :first_name => "Ben", :last_name => "Johnson"}
+ assert_redirected_to account_url
+ follow_redirect!
+ assert flash.key?(:notice)
+ assert_template "users/show"
+
+ access_account
+ logout
+
+ # Try to access my account again
+ get account_url
+ assert_redirected_to new_user_session_url
+ assert flash.key?(:notice)
+
+ login_successfully("bjohnson", "sillywilly")
+ access_account
+ end
+end
View
158 test_app/test/integration/user_session_test.rb
@@ -0,0 +1,158 @@
+require 'test_helper'
+
+# I know these tests are not really integration tests, but since UserSessions deals with cookies, models, etc. It was easiest and best to test it via an integration.
+class UserSessionTest < ActionController::IntegrationTest
+ def test_activated
+ UserSession.controller = nil
+ assert !UserSession.activated?
+ get new_user_session_url # reactive
+ assert UserSession.activated?
+ end
+
+ def test_create
+ assert !UserSession.create("unknown", "bad")
+ assert UserSession.create("bjohnson", "benrocks")
+ assert_raise(Authgasm::Session::SessionInvalid) { assert !UserSession.create!("unknown", "bad") }
+ assert_nothing_raised { UserSession.create!("bjohnson", "benrocks") }
+ end
+
+ def test_klass
+ assert_equal User, UserSession.klass
+ end
+
+ def test_klass_name
+ assert_equal "User", UserSession.klass_name
+ end
+
+ def test_find
+ assert_equal nil, UserSession.find
+ post user_sessions_url, {:user_session => {:login => "bjohnson", :password => "benrocks"}}
+ assert UserSession.find
+ end
+
+ def test_initialize
+ session = UserSession.new
+ assert !session.valid?
+ assert_equal nil, session.login
+ assert_equal nil, session.unauthorized_record
+
+ session = UserSession.new(:secure)
+ assert_equal :secure, session.id
+ assert !session.valid?
+ assert_equal nil, session.login
+ assert_equal nil, session.unauthorized_record
+
+ session = UserSession.new("user", "pass")
+ assert_equal nil, session.id
+ assert !session.valid?
+ assert_equal "user", session.login
+ assert_equal nil, session.unauthorized_record
+
+ session = UserSession.new("user", "pass", :secure)
+ assert_equal :secure, session.id
+ assert !session.valid?
+ assert_equal "user", session.login
+ assert_equal nil, session.unauthorized_record
+
+ session = UserSession.new(:login => "user", :password => "pass")
+ assert_equal nil, session.id
+ assert !session.valid?
+ assert_equal "user", session.login
+ assert_equal nil, session.unauthorized_record
+
+ session = UserSession.new({:login => "user", :password => "pass"}, :secure)
+ assert_equal :secure, session.id
+ assert !session.valid?
+ assert_equal "user", session.login
+ assert_equal nil, session.unauthorized_record
+
+ session = UserSession.new(users(:ben))
+ assert_equal nil, session.id
+ assert session.valid?
+ assert_equal nil, session.login
+ assert_equal users(:ben), session.unauthorized_record
+
+ session = UserSession.new(users(:ben), :secure)
+ assert_equal :secure, session.id
+ assert session.valid?
+ assert_equal nil, session.login
+ assert_equal users(:ben), session.unauthorized_record
+ end
+
+ def test_credentials
+ session = UserSession.new
+ session.credentials = nil
+ assert_equal({:login => nil, :password => "<Protected>"}, session.credentials)
+
+ session = UserSession.new
+ session.credentials = {:login => "ben"}
+ assert_equal({:login => "ben", :password => "<Protected>"}, session.credentials)
+
+ session = UserSession.new
+ assert_raise(ArgumentError) { session.credentials = {:login => "ben", :random_field => "test"} }
+
+ session = UserSession.new
+ session.credentials = {:login => "ben", :password => "awesome"}
+ assert_equal({:login => "ben", :password => "<Protected>"}, session.credentials)
+ assert_equal "awesome", session.send(:protected_password)
+ end
+
+ def test_destroy
+ # tested thoroughly in stories
+ end
+
+ def test_errors
+ # don't need to go crazy here since we are using ActiveRecord's error class, which has been thorough tested there
+ session = UserSession.new
+ assert !session.valid?
+ assert session.errors.on(:login)
+ assert session.errors.on(:password)
+ end
+
+ def test_id
+ session = UserSession.new
+ assert_equal nil, session.id
+ session.id = :secure
+ assert_equal :secure, session.id
+ end
+
+ def test_inspect
+ session = UserSession.new
+ assert_equal "#<UserSession {:login=>nil, :password=>\"<protected>\"}>", session.inspect
+
+ session = UserSession.new("user", "pass")
+ assert_equal "#<UserSession {:login=>\"user\", :password=>\"<protected>\"}>", session.inspect
+
+ session = UserSession.new(users(:ben))
+ assert_equal "#<UserSession {:unauthorized_record=>\"<protected>\"}>", session.inspect
+ end
+
+ def test_new_session
+ session = UserSession.new
+ assert session.new_session?
+
+ session.login = "bjohnson"
+ session.password = "benrocks"
+ session.save
+ assert !session.new_session?
+
+ login_successfully("bjohnson", "benrocks")
+ session = UserSession.find
+ assert !session.new_session?
+ end
+
+ def test_remember_me
+ session = UserSession.new
+ session.remember_me = true
+ assert_equal 3.months, session.remember_me_for
+ assert session.remember_me_until > Time.now
+
+ session.remember_me = false
+ assert_equal nil, session.remember_me_for
+ assert_equal nil, session.remember_me_until
+ end
+
+ def test_save
+ # tested thoroughly in stories
+ end
+end
View
47 test_app/test/test_helper.rb
@@ -33,6 +33,51 @@ class Test::Unit::TestCase
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
+end
- # Add more helper methods to be used by all tests here...
+class ActionController::IntegrationTest
+ def setup
+ get new_user_session_url # to active authgasm
+ end
+
+ def teardown
+ Authgasm::Session::Base.controller = nil
+ end
+
+ private
+ def login_successfully(login, password)
+ post user_sessions_url, :user_session => {:login => login, :password => password}
+ assert_redirected_to account_url
+ follow_redirect!
+ assert_template "users/show"
+ end
+
+ def login_unsuccessfully(login = nil, password = nil)
+ params = (login || password) ? {:user_session => {:login => login, :password => password}} : nil
+ post user_sessions_url, params
+ assert_template "user_sessions/new"
+ end
+
+ def access_account(user = nil)
+ user ||= users(:ben)
+ # Perform multiple requests to make sure the session is persisting properly, just being anal here
+ 3.times do
+ get account_url
+ assert_equal user.id, session["user_id"]
+ assert_equal user.remember_token, cookies["user_credentials"]
+ assert_response :success
+ assert_template "users/show"
+ end
+ end
+
+ def logout(alt_redirect = nil)
+ redirecting_to = alt_redirect || new_user_session_url
+ get logout_url
+ assert_redirected_to redirecting_to # because I tried to access registration above, and it stored it
+ follow_redirect!
+ assert flash.key?(:notice)
+ assert_equal nil, session["user_id"]
+ assert_equal "", cookies["user_credentials"]
+ assert_template redirecting_to.gsub("http://www.example.com/", "")
+ end
end
View
8 test_app/test/unit/ass_test.rb
@@ -1,8 +0,0 @@
-require 'test_helper'
-
-class AssTest < ActiveSupport::TestCase
- # Replace this with your real tests.
- def test_truth
- assert true
- end
-end

0 comments on commit 35f14ba

Please sign in to comment.