Skip to content

Commit

Permalink
Added in authentication by params / token
Browse files Browse the repository at this point in the history
  • Loading branch information
binarylogic committed Nov 13, 2008
1 parent 62ac95a commit b83abca
Show file tree
Hide file tree
Showing 19 changed files with 368 additions and 168 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.rdoc
@@ -1,4 +1,4 @@
== 1.0.1 released 2008-11-05
== 1.1.0 released 2008-11-05

* Moved Rack standards into abstract_adapter for the controllers.
* Added logging_in_with_credentials?, logging_in_with_unauthorized_record?
Expand All @@ -13,6 +13,8 @@
* Added last_request_at_threshold configuration option.
* Changed Scoped class to AuthenticatesManyAssociation, like AR has HasManyAssociation, etc.
* Added should_be_authentic shoulda macro.
* Removed some magic from how sessions are initialized. See the initialize documentation, this method is a little more structured now, which was require for adding in openid.
* Added in logging via a params token, which is friendly for feed URLs. Works just like cookies and sessions when persisting the session.

== 1.0.0 released 2008-11-05

Expand Down
4 changes: 4 additions & 0 deletions lib/authlogic.rb
Expand Up @@ -24,6 +24,8 @@
require File.dirname(__FILE__) + "/authlogic/session/config"
require File.dirname(__FILE__) + "/authlogic/session/cookies"
require File.dirname(__FILE__) + "/authlogic/session/errors"
require File.dirname(__FILE__) + "/authlogic/session/openid"
require File.dirname(__FILE__) + "/authlogic/session/params"
require File.dirname(__FILE__) + "/authlogic/session/session"
require File.dirname(__FILE__) + "/authlogic/session/scopes"
require File.dirname(__FILE__) + "/authlogic/session/base"
Expand All @@ -36,6 +38,8 @@ class Base
include ActiveRecordTrickery
include Callbacks
include Cookies
#include OpenID
include Params
include Session
include Scopes
end
Expand Down
Expand Up @@ -22,7 +22,8 @@ module ActsAsAuthentic
# user.password= Method name based on the :password_field option. This is used to set the password. Pass the *raw* password to this.
# user.confirm_password= Confirms the password, needed to change the password.
# user.valid_password?(pass) Determines if the password passed is valid. The password could be encrypted or raw.
# user.reset_password! Basically resets the password to a random password using only letters and numbers.
# user.reset_password Resets the password to a random password using only letters and numbers.
# user.reset_password! The same as reset_password but saves the record.
# 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 and session invalid. A way to log the user out withouth changing their password.
#
Expand Down
Expand Up @@ -5,6 +5,7 @@ module Credentials # :nodoc:
def acts_as_authentic_with_credentials(options = {})
acts_as_authentic_without_credentials(options)

# The following helps extract configuration into their specific ORM adapter and allows the Session configuration to set itself based on these values
class_eval <<-"end_eval", __FILE__, __LINE__
def self.login_field
@login_field ||= #{options[:login_field].inspect} ||
Expand Down Expand Up @@ -39,6 +40,13 @@ def self.password_salt_field
end
end_eval

# The following methods allow other focused modules to alter validation behavior, such as openid as an alternate login
unless respond_to?(:allow_blank_for_login_validations?)
def self.allow_blank_for_login_validations?
false
end
end

options[:crypto_provider] ||= CryptoProviders::Sha512
options[:login_field_type] ||= login_field == :email ? :email : :login

Expand All @@ -53,7 +61,7 @@ def self.password_salt_field
options[:login_field_regex_message] ||= "should look like an email address."
validates_format_of login_field, :with => options[:login_field_regex], :message => options[:login_field_regex_message]
else
validates_length_of login_field, :within => 2..100
validates_length_of login_field, :within => 2..100, :allow_blank => true
options[:login_field_regex] ||= /\A\w[\w\.\-_@ ]+\z/
options[:login_field_regex_message] ||= "use only letters, numbers, spaces, and .-_@ please."
validates_format_of login_field, :with => options[:login_field_regex], :message => options[:login_field_regex_message]
Expand Down Expand Up @@ -81,23 +89,28 @@ def #{password_field}=(pass)
self.#{password_salt_field} = self.class.unique_token
self.#{crypted_password_field} = crypto_provider.encrypt(@#{password_field} + #{password_salt_field})
end
def valid_#{password_field}?(attempted_password)
return false if attempted_password.blank? || #{crypted_password_field}.blank? || #{password_salt_field}.blank?
attempted_password == #{crypted_password_field} ||
(crypto_provider.respond_to?(:decrypt) && crypto_provider.decrypt(#{crypted_password_field}) == attempted_password + #{password_salt_field}) ||
(!crypto_provider.respond_to?(:decrypt) && crypto_provider.encrypt(attempted_password + #{password_salt_field}) == #{crypted_password_field})
end
def #{password_field}; end
def confirm_#{password_field}; end
def reset_#{password_field}!
def reset_#{password_field}
chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
newpass = ""
1.upto(10) { |i| newpass << chars[rand(chars.size-1)] }
self.#{password_field} = newpass
self.confirm_#{password_field} = newpass
end
alias_method :randomize_password, :reset_password
def reset_#{password_field}!
reset_#{password_field}
save_without_session_maintenance(false)
end
alias_method :randomize_password!, :reset_password!
Expand All @@ -106,7 +119,7 @@ def reset_#{password_field}!
def tried_to_set_password?
tried_to_set_password == true
end
def validate_password
if new_record? || tried_to_set_#{password_field}?
if @#{password_field}.blank?
Expand Down
2 changes: 1 addition & 1 deletion lib/authlogic/session/authenticates_many_association.rb
Expand Up @@ -2,7 +2,7 @@ module Authlogic
module Session
# = Authenticates Many Association
#
# This object is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details and call them on an object, which allows you to do the following:
# An object of this class is used as a proxy for the authenticates_many relationship. It basically allows you to "save" scope details and call them on an object, which allows you to do the following:
#
# @account.user_sessions.new
# @account.user_sessions.find
Expand Down
49 changes: 25 additions & 24 deletions lib/authlogic/session/base.rb
Expand Up @@ -80,41 +80,38 @@ def controllers
end
end

attr_accessor :login_with, :new_session
attr_accessor :new_session
attr_reader :record, :unauthorized_record
attr_writer :id
attr_writer :id, :login_with

# You can initialize a session by doing any of the following:
#
# UserSession.new
# UserSession.new(login, password)
# UserSession.new(:login => login, :password => password)
# UserSession.new(User.first)
# UserSession.new(:login => "login", :password => "password", :remember_me => true)
# UserSession.new(:openid => "identity url", :remember_me => true)
# UserSession.new(User.first, true)
#
# If a user has more than one session you need to pass an id so that Authlogic knows how to differentiate the sessions. The id MUST be a Symbol.
#
# UserSession.new(:my_id)
# UserSession.new(login, password, :my_id)
# UserSession.new({:login => loing, :password => password}, :my_id)
# UserSession.new(User.first, :my_id)
# UserSession.new({:login => "login", :password => "password", :remember_me => true}, :my_id)
# UserSession.new({:openid => "identity url", :remember_me => true}, :my_id)
# UserSession.new(User.first, true, :my_id)
#
# 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. Authlogic will separate everything out.
#
# The reason the id is separate from the first parameter hash is becuase this should be controlled by you, not by what the user passes. A usr could inject their own id and things would not work as expected.
def initialize(*args)
raise NotActivated.new(self) unless self.class.activated?

create_configurable_methods!

self.id = args.pop if args.last.is_a?(Symbol)

case args.first
when Hash
if args.first.is_a?(Hash)
self.credentials = args.first
when String
send("#{login_field}=", args[0]) if args.size > 0
send("#{password_field}=", args[1]) if args.size > 1
self.remember_me = args[2] if args.size > 2
else
elsif !args.first.blank? && args.first.class < ::ActiveRecord::Base
self.unauthorized_record = args.first
self.remember_me = args[1] if args.size > 1
end
Expand All @@ -131,7 +128,7 @@ def credentials=(values)
return if values.blank? || !values.is_a?(Hash)
values.symbolize_keys!
[login_field.to_sym, password_field.to_sym, :remember_me].each do |field|
next if !values.key?(field)
next if values[field].blank?
send("#{field}=", values[field])
end
end
Expand Down Expand Up @@ -210,6 +207,17 @@ def inspect # :nodoc:
"#<#{self.class.name} #{details.inspect}>"
end

# A flag for how the user is logging in. Possible values:
#
# * :credentials - username and password
# * :unauthorized_record - an actual ActiveRecord object
# * :openid - OpenID
#
# By default this is :credentials
def login_with
@login_with ||= :credentials
end

# Returns true if logging in with credentials. Credentials mean username and password.
def logging_in_with_credentials?
login_with == :credentials
Expand Down Expand Up @@ -364,11 +372,7 @@ def record=(value)
end

def search_for_record(method, value)
begin
klass.send(method, value)
rescue Exception
raise method.inspect + " " + value.inspect
end
klass.send(method, value)
end

def valid_credentials?
Expand Down Expand Up @@ -403,9 +407,6 @@ def valid_credentials?
errors.add_to_base("You can not login with a new record.")
return false
end
else
errors.add_to_base("You must provide some form of credentials before logging in.")
return false
end

self.record = unchecked_record
Expand Down
49 changes: 42 additions & 7 deletions lib/authlogic/session/config.rb
Expand Up @@ -103,13 +103,14 @@ def find_by_openid_method(value = nil)
end
alias_method :find_by_openid_method=, :find_by_openid_method

# Calling UserSession.find tries to find the user session by session, then cookie, then basic http auth. This option allows you to change the order or remove any of these.
# Calling UserSession.find tries to find the user session by session, then cookie, then params, and finally by basic http auth.
# This option allows you to change the order or remove any of these.
#
# * <tt>Default:</tt> [:session, :cookie, :http_auth]
# * <tt>Default:</tt> [:session, :cookie, :params, :http_auth]
# * <tt>Accepts:</tt> Array, and can only use any of the 3 options above
def find_with(*values)
if values.blank?
read_inheritable_attribute(:find_with) || find_with(:session, :cookie, :http_auth)
read_inheritable_attribute(:find_with) || find_with(:session, :cookie, :params, :http_auth)
else
values.flatten!
write_inheritable_attribute(:find_with, values)
Expand Down Expand Up @@ -180,6 +181,24 @@ def openid_file_store_path(value = nil)
end
alias_method :openid_file_store_path=, :openid_file_store_path

# Works exactly like cookie_key, but for params. So a user can login via params just like a cookie or a session. Your URK would look like:
#
# http://www.domain.com?user_credentials=fdsfdfd32jfksdjfdksl
#
# You can change the "user_credentials" key above with this configuration option. Keep in mind, just like cookie_key, if you supply an id
# the id will be appended to the front.
#
# * <tt>Default:</tt> cookie_key
# * <tt>Accepts:</tt> String
def params_key(value = nil)
if value.nil?
read_inheritable_attribute(:params_key) || params_key(cookie_key)
else
write_inheritable_attribute(:params_key, value)
end
end
alias_method :params_key=, :params_key

# Works exactly like login_field, but for the password instead.
#
# * <tt>Default:</tt> Guesses based on the model columns, tries password and pass. If none are present it defaults to password
Expand Down Expand Up @@ -263,14 +282,17 @@ def verify_password_method(value = nil)

module InstanceMethods # :nodoc:
def cookie_key
key_parts = [id, scope[:id], self.class.cookie_key].compact
key_parts.join("_")
build_key(self.class.cookie_key)
end

def find_by_login_method
self.class.find_by_login_method
end

def find_by_openid_method
self.class.find_by_openid_method
end

def find_with
self.class.find_with
end
Expand All @@ -282,6 +304,14 @@ def last_request_at_threshold
def login_field
self.class.login_field
end

def openid_field
self.class.openid_field
end

def params_key
build_key(self.class.params_key)
end

def password_field
self.class.password_field
Expand All @@ -297,13 +327,18 @@ def remember_token_field
end

def session_key
key_parts = [id, scope[:id], self.class.session_key].compact
key_parts.join("_")
build_key(self.class.session_key)
end

def verify_password_method
self.class.verify_password_method
end

private
def build_key(last_part)
key_parts = [id, scope[:id], last_part].compact
key_parts.join("_")
end
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions lib/authlogic/session/cookies.rb
@@ -1,5 +1,8 @@
module Authlogic
module Session
# = Cookies
#
# Handles all authentication that deals with cookies, such as persisting a session and saving / destroying a session.
module Cookies
def self.included(klass)
klass.after_save :save_cookie
Expand Down

0 comments on commit b83abca

Please sign in to comment.