brought submodules into the main repo

This commit is contained in:
Espen Antonsen 2011-04-11 18:07:31 +08:00
parent b412686030
commit b3b44c0f46
155 changed files with 8422 additions and 9 deletions

6
.gitmodules vendored
View file

@ -1,6 +0,0 @@
[submodule "vendor/plugins/acts_as_permissible"]
path = vendor/plugins/acts_as_permissible
url = git://github.com/espen/acts_as_permissible.git
[submodule "vendor/plugins/authlogic"]
path = vendor/plugins/authlogic
url = git://github.com/espen/authlogic.git

View file

@ -1,6 +1,6 @@
source 'http://rubygems.org' source 'http://rubygems.org'
gem 'rails', '3.0.3' gem 'rails'
gem 'mime-types', :require => 'mime/types' gem 'mime-types', :require => 'mime/types'
gem "carrierwave", :git => 'git://github.com/jnicklas/carrierwave.git' gem "carrierwave", :git => 'git://github.com/jnicklas/carrierwave.git'

@ -1 +0,0 @@
Subproject commit bdc96b55ec30704e1c5b2fe04f4840a8e8692418

View file

@ -0,0 +1 @@
.DS_Store

View file

@ -0,0 +1,20 @@
Copyright (c) 2008 [name of plugin creator]
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,68 @@
ActsAsPermissible
=================
Source:
http://github.com/NoamB/acts_as_permissible/tree/master
More information at:
http://github.com/NoamB/acts_as_permissible/wikis
This plugin enables any activerecord model to have permissions.
It provides a set of methods for querying the model's permissions.
In addition, the plugin can generate roles support, which turns in into a full RBAC (Role Based Access Control) solution.
Any model which includes the line "acts_as_permissible" can have permissions, and with roles support it can also have roles which in turn have their own permissions.
Roles can also belong to roles, which creates a sort of inheritance hierarchy.
When permissions are calculated, the model's permissions are merged with the model's role permissions (if any), which in turn are merged with the role's roles permissions, until a finite permissions hash is generated.
In the case of identical keys, a false value overrides a true value, A true value overrides a nil value, and a nil value is false.
Setup
=====
script/generate permissible <PermissionModelName> [RoleModelName]
The role model name is optional. If you do not want the roles support generated, use the --skip-roles option.
examples: script/generate permissible Permission Role
script/generate permissible Permission Group
script/generate permissible Allowance --skip-roles
use --skip-migration if you don't want a migration created for the permissions model.
use --rspec to force rspec tests installed (currenty these are the only ones available).
Add any permissions you want to your permissions table.
Add any roles you want to your roles table.
Add user->role relationships in your roles_memberships table.
Add role->role relationships in your roles_memberships table.
Usage
=====
class User < Activerecord::Base
acts_as_permissible
end
Now a user will have the following methods:
@user.permissions_hash() # => {:view_something => true, :delete_something => false}
@user.has_permission?("view_something") # => true
@user.has_permission?("view_something", "delete_something") # => false
@user.has_permission?("delete_something") # => false
@user.has_permission?("create_something") # => false
@user.permissions_hash() # => {:view_something => true, :delete_something => false}
@user.permissions << Permission.new(:action => "new_thing", :granted => true)
@user.permissions_hash() # => {:view_something => true, :delete_something => false}
@user.reload_permissions!() # => {:view_something => true, :delete_something => false, :new_thing => true}
@user.permissions_hash() # => {:view_something => true, :delete_something => false, :new_thing => true}
# this is useful for getting the hash again into memory after the permissions table was updated.
And with roles support:
@user.in_role?("publisher") # => true
@user.in_role?("publisher","advertiser") # => false
@user.in_any_role?("publisher","advertiser") => true
@user.full_permissions_hash() # will return a merged hash of user and roles permissions.
Copyright (c) 2008 Noam Ben-Ari, released under the MIT license

View file

@ -0,0 +1,22 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
desc 'Default: run unit tests.'
task :default => :test
desc 'Test the acts_as_permissible plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end
desc 'Generate documentation for the acts_as_permissible plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'ActsAsPermissible'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end

View file

@ -0,0 +1 @@
./script/generate permissible <PermissionsModelName> [RolesModelName | --skip-roles] [--skip-migrations] [--rspec]

View file

@ -0,0 +1,154 @@
#require 'acts_as_permissible/rails_commands'
class PermissibleGenerator < Rails::Generator::NamedBase
default_options :skip_migrations => false, :skip_timestamps => false
attr_reader :role_model_name
attr_reader :role_model_file_name
attr_reader :role_membership_model_name
attr_reader :role_membership_model_file_name
def initialize(runtime_args, runtime_options = {})
super
unless options[:skip_roles]
if @args.first.blank?
puts "No Roles model name supplied! Please use --skip-roles if you do not want roles support generated."
exit()
end
@role_model_name = @args.first
@role_model_file_name = role_model_name.underscore
@role_membership_model_name = role_model_name + "Membership"
@role_membership_model_file_name = role_membership_model_name.underscore
end
@rspec = has_rspec?
end
def manifest
recorded_session = record do |m|
m.directory File.join('app/models', class_path)
if @rspec
m.directory File.join('spec/models', class_path)
m.directory File.join('spec/fixtures', class_path)
# else
# m.directory File.join('test/unit', class_path)
end
m.template 'model.rb',
File.join('app/models',
class_path,
"#{file_name}.rb")
m.template 'acts_as_permissible.rb',
File.join('lib',
"acts_as_permissible.rb")
# m.template 'initializer.rb',
# File.join('config/initializers',
# "acts_as_permissible_init.rb")
unless options[:skip_roles]
m.template 'role_model.rb',
File.join('app/models',
"#{role_model_file_name}.rb")
m.template 'role_membership_model.rb',
File.join('app/models',
"#{role_membership_model_file_name}.rb")
end
if @rspec
m.template 'model_spec.rb',
File.join('spec/models',
class_path,
"#{file_name}_spec.rb")
m.template 'fixtures.yml',
File.join('spec/fixtures',
"#{table_name}.yml")
m.template 'acts_as_permissible_spec.rb',
File.join('spec/models',
"acts_as_permissible_spec.rb")
unless options[:skip_roles]
m.template 'role_model_spec.rb',
File.join('spec/models',
"#{role_model_file_name}_spec.rb")
m.template 'role_membership_model_spec.rb',
File.join('spec/models',
"#{role_model_file_name}_membership_spec.rb")
m.template 'role_model_fixtures.yml',
File.join('spec/fixtures',
"#{role_model_file_name.pluralize}.yml")
m.template 'role_membership_model_fixtures.yml',
File.join('spec/fixtures',
"#{role_membership_model_file_name.pluralize}.yml")
end
# else
# m.template 'unit_test.rb',
# File.join('test/unit',
# class_path,
# "#{file_name}_test.rb")
# m.template 'fixtures.yml',
# File.join('test/fixtures',
# "#{table_name}.yml")
end
unless options[:skip_migrations]
m.migration_template 'migration.rb', 'db/migrate', :assigns => {
:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
}, :migration_file_name => "create_#{file_name.pluralize}"
unless options[:skip_roles]
m.migration_template 'role_migration.rb', 'db/migrate', :assigns => {
:migration_name => "Create#{role_model_name.pluralize.gsub(/::/, '')}"
}, :migration_file_name => "create_#{role_model_file_name.pluralize}"
m.migration_template 'role_membership_migration.rb', 'db/migrate', :assigns => {
:migration_name => "Create#{role_membership_model_name.pluralize.gsub(/::/, '')}"
}, :migration_file_name => "create_#{role_membership_model_file_name.pluralize}"
end
end
end
action = nil
action = $0.split("/")[1]
case action
when "generate"
puts
puts ("-" * 70)
puts
puts "acts_as_permissible"
puts
puts ("-" * 70)
puts
when "destroy"
puts
puts ("-" * 70)
puts
puts "Thanks for using acts_as_permissible"
puts
puts ("-" * 70)
puts
else
puts
end
recorded_session
end
def has_rspec?
options[:rspec] || (File.exist?('spec') && File.directory?('spec'))
end
protected
def banner
"Usage: #{$0} permissible <PermissionsModelName> [RoleModelName]"
end
def add_options!(opt)
opt.separator ''
opt.separator 'Options:'
opt.on("--skip-roles",
"Don't generate roles support") { |v| options[:skip_roles] = v }
opt.on("--skip-migrations",
"Don't generate migration files for these models") { |v| options[:skip_migrations] = v }
opt.on("--skip-timestamps",
"Don't add timestamps to the migration file for this model") { |v| options[:skip_timestamps] = v }
opt.on("--rspec",
"Force rspec mode (checks for RAILS_ROOT/spec by default)") { |v| options[:rspec] = true }
end
end

View file

@ -0,0 +1,106 @@
# ActsAsPermissible
module NoamBenAri
module Acts #:nodoc:
module Permissible #:nodoc:
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def acts_as_permissible
has_many :<%= table_name %>, :as => :permissible, :dependent => :destroy
<% unless options[:skip_roles] %>
has_many :<%= role_membership_model_file_name.pluralize %>, :as => :roleable, :dependent => :destroy
has_many :<%= role_model_file_name.pluralize %>, :through => :<%= role_membership_model_file_name.pluralize %>, :source => :<%= role_model_file_name %>
<% end %>
include NoamBenAri::Acts::Permissible::InstanceMethods
extend NoamBenAri::Acts::Permissible::SingletonMethods
alias_method :full_permissions_hash, :permissions_hash
end
end
# This module contains class methods
module SingletonMethods
# Helper method to lookup for permissions for a given object.
# This method is equivalent to obj.permissions.
def find_permissions_for(obj)
permissible = ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s
<%= class_name %>.find(:all,
:conditions => ["permissible_id = ? and permissible_type = ?", obj.id, permissible]
)
end
end
# This module contains instance methods
module InstanceMethods
# returns permissions in hash form
# from all levels recursively
def permissions_hash
@permissions_hash ||= lambda do
@permissions_hash = <%= table_name %>.inject({}) { |hsh,perm| hsh.merge(perm.to_hash) }.symbolize_keys!
<% unless options[:skip_roles] -%><%= role_model_file_name %>s.each do |<%= role_model_file_name %>|
merge_permissions!(<%= role_model_file_name %>.permissions_hash)
end
<% end -%>@permissions_hash
end.call()
end
# accepts a permission identifier string or an array of permission identifier strings
# and return true if the user has all of the permissions given by the parameters
# false if not.
def has_permission?(*perms)
perms.all? {|perm| permissions_hash.include?(perm.to_sym) && (permissions_hash[perm.to_sym] == true) }
end
# accepts a permission identifier string or an array of permission identifier strings
# and return true if the user has any of the permissions given by the parameters
# false if none.
def has_any_permission?(*perms)
perms.any? {|perm| permissions_hash.include?(perm.to_sym) && (permissions_hash[perm.to_sym] == true) }
end
# Merges another permissible object's permissions into this permissible's permissions hash
# In the case of identical keys, a false value wins over a true value.
def merge_permissions!(other_permissions_hash)
permissions_hash.merge!(other_permissions_hash) {|key,oldval,newval| oldval.nil? ? newval : oldval && newval}
end
# Resets permissions and then loads them.
def reload_permissions!
reset_permissions!
permissions_hash
end
<% unless options[:skip_roles] %>
def <%= role_model_file_name %>s_list
list = []
<%= role_model_file_name %>s.inject(list) do |list,<%= role_model_file_name %>|
list << <%= role_model_file_name %>.name
<%= role_model_file_name %>.<%= role_model_file_name.pluralize %>_list.inject(list) {|list,<%= role_model_file_name %>| list << <%= role_model_file_name %>}
end
list.uniq
end
def in_<%= role_model_file_name %>?(*<%= role_model_file_name %>_names)
<%= role_model_file_name %>_names.all? {|<%= role_model_file_name %>| <%= role_model_file_name %>s_list.include?(<%= role_model_file_name %>) }
end
def in_any_<%= role_model_file_name %>?(*<%= role_model_file_name %>_names)
<%= role_model_file_name %>_names.any? {|<%= role_model_file_name %>| <%= role_model_file_name %>s_list.include?(<%= role_model_file_name %>) }
end
<% end %>
private
# Nilifies permissions_hash instance variable.
def reset_permissions!
@permissions_hash = nil
end
end
end
end
end

View file

@ -0,0 +1,208 @@
require File.dirname(__FILE__) + '/../spec_helper'
class <%= class_name %> < ActiveRecord::Base
acts_as_permissible
end
describe "acts_as_permissible" do
fixtures :<%= table_name %>
before(:each) do
@perm = <%= table_name %>(:perm)
end
describe "class methods" do
it "should find_permissions_for(obj) correctly" do
<%= class_name %>.find_permissions_for(@perm).size.should == 2
<%= class_name %>.find_permissions_for(@perm).first.action.should == "view_something"
<%= class_name %>.find_permissions_for(@perm).last.action.should == "delete_something"
end
end
describe "permissions_hash" do
it "should return the correct permissions_hash" do
@perm.permissions_hash.should == {:view_something => true, :delete_something => false}
end
end
describe "has_permission?" do
it "should return true if permission found" do
@perm.has_permission?("view_something").should == true
end
it "should return false if permission not found" do
@perm.has_permission?("create_something").should == false
end
it "should return false if permission found and is denied" do
@perm.has_permission?("delete_something").should == false
end
end
describe "merge_permissions!" do
before(:each) do
@perm2 = <%= table_name %>(:perm2)
@merged_permissions = @perm.merge_permissions!(@perm2.permissions_hash)
# {:update_something=>true, :view_something=>true, :delete_something=>false, :create_something=>false}
end
it "should include all keys from both hashes" do
@merged_permissions.keys.should ==
(@perm.permissions_hash.keys + @perm2.permissions_hash.keys).uniq
end
it "should override identical keys with false value" do
@merged_permissions[:delete_something].should == false
end
end
describe "reload_permissions!" do
before(:each) do
@original_hash = @perm.permissions_hash
@perm.<%= table_name %> << <%= class_name %>.new(:action => "add_something", :granted => true)
end
it "should catch-up with database changes" do
@perm.permissions_hash.should == @original_hash
reloaded_hash = @perm.reload_permissions!
reloaded_hash.should_not == @original_hash
end
it "should get the changes correctly" do
reloaded_hash = @perm.reload_permissions!
reloaded_hash.keys.should include(:add_something)
end
end
<% unless options[:skip_roles] %>
describe "<%= role_model_file_name.pluralize %>_list" do
before(:each) do
@perm.<%= role_model_file_name.pluralize %>_list.should == []
@mutables = <%= role_model_name %>.new(:name => "mutables")
@mutables.save!
@wierdos = <%= role_model_name %>.new(:name => "wierdos")
@wierdos.save!
@mutables.<%= role_model_file_name.pluralize %> << @wierdos
end
after(:each) do
@mutables.destroy
@wierdos.destroy
@perm.<%= role_model_file_name.pluralize %>.reset
@perm.<%= role_model_file_name.pluralize %>_list.should == []
end
it "should return the correct list" do
@perm.<%= role_model_file_name.pluralize %> << @wierdos
@perm.<%= role_model_file_name.pluralize %>_list.size.should == 1
@perm.<%= role_model_file_name.pluralize %>_list.should include("wierdos")
end
it "should return the correct list including parent <%= role_model_file_name.pluralize %> of <%= role_model_file_name.pluralize %> recursively." do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.<%= role_model_file_name.pluralize %>_list.size.should == 2
@perm.<%= role_model_file_name.pluralize %>_list.should include("mutables")
@perm.<%= role_model_file_name.pluralize %>_list.should include("wierdos")
end
end
describe "in_<%= role_model_file_name %>?" do
before(:each) do
@mutables = <%= role_model_name %>.new(:name => "mutables")
@mutables.save!
@immutables = <%= role_model_name %>.new(:name => "immutables")
@immutables.save!
end
after(:each) do
@mutables.destroy
@immutables.destroy
@perm.<%= role_model_file_name.pluralize %>.reset
end
it "should return true if member of one" do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.in_<%= role_model_file_name %>?("mutables").should == true
end
it "should return false if not a member" do
@perm.in_<%= role_model_file_name %>?("mutables").should == false
end
it "should return true if member of all" do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.<%= role_model_file_name.pluralize %> << @immutables
@perm.in_<%= role_model_file_name %>?("mutables","immutables").should == true
end
it "should return false if member of some" do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.in_<%= role_model_file_name %>?("mutables","immutables").should == false
end
end
describe "in_any_<%= role_model_file_name %>?" do
before(:each) do
@mutables = <%= role_model_name %>.new(:name => "mutables")
@mutables.save!
@immutables = <%= role_model_name %>.new(:name => "immutables")
@immutables.save!
end
it "should return true if member of one" do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.in_any_<%= role_model_file_name %>?("mutables","immutables").should == true
end
it "should return false if not a member" do
@perm.in_any_<%= role_model_file_name %>?("mutables","immutables").should == false
end
it "should return true if member of all" do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.<%= role_model_file_name.pluralize %> << @immutables
@perm.in_any_<%= role_model_file_name %>?("mutables","immutables").should == true
end
end
describe "full_permissions_hash" do
before(:each) do
@mutables = <%= role_model_name %>.new(:name => "mutables")
@mutables.save!
@mutable_permission = <%= class_name %>.new(:permissible_id => @mutables.id, :permissible_type => @mutables.class.to_s, :action => "view_something", :granted => false)
@mutable_permission.save!
@immutables = <%= role_model_name %>.new(:name => "immutables")
@immutables.save!
@immutable_permission = <%= class_name %>.new(:permissible_id => @immutables.id, :permissible_type => @immutables.class.to_s, :action => "download_something", :granted => true)
@immutable_permission.save!
end
it "should return the correct hash if object doesn't belong to <%= role_model_file_name.pluralize %>" do
@perm.<%= role_model_file_name.pluralize %>.should == []
@perm.full_permissions_hash.should == {:view_something=>true, :delete_something=>false}
end
it "should return the correct hash if object belongs to one <%= role_model_file_name %>" do
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.full_permissions_hash.should == {:view_something=>false, :delete_something=>false}
end
it "should return the correct hash if object belongs to one <%= role_model_file_name %> which belongs to another <%= role_model_file_name %>" do
@mutables.<%= role_model_file_name.pluralize %> << @immutables
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.full_permissions_hash.should == {:view_something=>false, :delete_something=>false, :download_something=>true}
end
it "should return the correct hash if object belongs to 2 <%= role_model_file_name.pluralize %>" do
@perm.<%= role_model_file_name.pluralize %> << @immutables
@perm.<%= role_model_file_name.pluralize %> << @mutables
@perm.full_permissions_hash.should == {:view_something=>false, :delete_something=>false, :download_something=>true}
end
after(:each) do
@mutables.destroy
@immutables.destroy
@perm.<%= role_model_file_name.pluralize %>.reset
end
end
<% end %>
end

View file

@ -0,0 +1,48 @@
one:
id: 1
permissible_id: 7
permissible_type: "<%= class_name %>"
action: "view_something"
granted: 1
two:
id: 2
permissible_id: 7
permissible_type: "<%= class_name %>"
action: "delete_something"
granted: 0
three:
id: 3
permissible_id: 8
permissible_type: "<%= class_name %>"
action: "view_something"
granted: 1
four:
id: 4
permissible_id: 8
permissible_type: "<%= class_name %>"
action: "delete_something"
granted: 1
five:
id: 5
permissible_id: 8
permissible_type: "<%= class_name %>"
action: "update_something"
granted: 1
six:
id: 6
permissible_id: 8
permissible_type: "<%= class_name %>"
action: "create_something"
granted: 0
perm:
id: 7
permissible_id: 47
permissible_type: "User"
action: "non_important"
granted: 1
perm2:
id: 8
permissible_id: 48
permissible_type: "User"
action: "non_important"
granted: 1

View file

@ -0,0 +1,2 @@
require 'lib/acts_as_permissible'
ActiveRecord::Base.send(:include, NoamBenAri::Acts::Permissible)

View file

@ -0,0 +1,16 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
create_table "<%= table_name %>", :force => true do |t|
t.integer :permissible_id
t.string :permissible_type
t.string :action
t.boolean :granted
<% unless options[:skip_timestamps] %>t.timestamps<% end %>
end
end
def self.down
drop_table "<%= table_name %>"
end
end

View file

@ -0,0 +1,19 @@
class <%= class_name %> < ActiveRecord::Base
# uncomment any of the following lines which is relevant to your application,
# or create your own with the name of the model which acts_as_permissible.
#belongs_to :user
<% unless options[:skip_roles] %>
belongs_to :<%= role_model_file_name %>
<% end %>
belongs_to :permissible, :polymorphic => true
validates_presence_of :permissible_id, :permissible_type, :action
validates_format_of :action, :with => /^[a-z_]+$/
validates_numericality_of :permissible_id
validates_uniqueness_of :action, :scope => [:permissible_id,:permissible_type]
def to_hash
self.new_record? ? {} : {self.action => self.granted}
end
end

View file

@ -0,0 +1,51 @@
require File.dirname(__FILE__) + '/../spec_helper'
describe <%= class_name %>, "to_hash" do
before(:each) do
@permission = <%= class_name %>.new(:permissible_id => 1, :permissible_type => "User", :action => "some_action", :granted => 1)
end
it "to_hash returns {} if new record" do
@permission.to_hash.should == {}
end
it "to_hash returns {action => granted}" do
@permission.save
@permission.to_hash.should == {"some_action" => true}
end
end
describe <%= class_name %>, "validations" do
before(:each) do
@permission = <%= class_name %>.new(:permissible_id => 1, :permissible_type => "User", :action => "some_action", :granted => 1)
end
it "should be valid" do
@permission.should be_valid
end
it "action should be unique to a permissible id and type" do
@permission.save
@permission2 = <%= class_name %>.new(:permissible_id => 1, :permissible_type => "User", :action => "some_action", :granted => 0)
@permission2.should_not be_valid
end
it "must have a permissible_id" do
@permission.permissible_id = nil
@permission.should_not be_valid
end
it "must have a permissible_type" do
@permission.permissible_type = nil
@permission.should_not be_valid
end
it "must have an action" do
@permission.action = nil
@permission.should_not be_valid
@permission.action = ""
@permission.should_not be_valid
end
end

View file

@ -0,0 +1,15 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
create_table :<%= role_membership_model_file_name %>s do |t|
t.integer :roleable_id
t.string :roleable_type
t.integer :<%= role_model_file_name %>_id
<% unless options[:skip_timestamps] %>t.timestamps<% end %>
end
end
def self.down
drop_table :<%= role_membership_model_file_name %>s
end
end

View file

@ -0,0 +1,36 @@
class <%= role_membership_model_name %> < ActiveRecord::Base
#belongs_to :user
belongs_to :<%= role_model_file_name %>
belongs_to :roleable, :polymorphic => true
validates_presence_of :roleable_id, :roleable_type, :<%= role_model_file_name %>_id
validates_uniqueness_of :<%= role_model_file_name %>_id, :scope => [:roleable_id, :roleable_type]
validates_numericality_of :roleable_id, :<%= role_model_file_name %>_id
validates_format_of :roleable_type, :with => /^[A-Z]{1}[a-z0-9]+([A-Z]{1}[a-z0-9]+)*$/
validate :<%= role_model_file_name %>_does_not_belong_to_itself_in_a_loop
protected
def <%= role_model_file_name %>_does_not_belong_to_itself_in_a_loop
if roleable_type == "<%= role_model_name %>"
if <%= role_model_file_name %>_id == roleable_id
errors.add_to_base("A <%= role_model_file_name %> cannot belong to itself.")
else
if belongs_to_itself_through_other?(roleable_id, <%= role_model_file_name %>_id)
errors.add_to_base("A <%= role_model_file_name %> cannot belong to a <%= role_model_file_name %> which belongs to it.")
end
end
end
end
def belongs_to_itself_through_other?(original_roleable_id, current_<%= role_model_file_name %>_id)
if self.class.find(:first, :select => "id", :conditions => ["roleable_id=? AND roleable_type='<%= role_model_name %>' AND <%= role_model_file_name %>_id=?",current_<%= role_model_file_name %>_id,original_roleable_id])
return true
else
memberships = self.class.find(:all, :select => "<%= role_model_file_name %>_id", :conditions => ["roleable_id=? AND roleable_type='<%= role_model_name %>'",current_<%= role_model_file_name %>_id])
if memberships.any? {|membership| belongs_to_itself_through_other?(original_roleable_id,membership.<%= role_model_file_name %>_id)}
return true
end
end
return false
end
end

View file

@ -0,0 +1,25 @@
publishers_to_customers:
id: 1
roleable_id: 1
roleable_type: "<%= role_model_name %>"
<%= role_model_file_name %>_id: 3
advertisers_to_customers:
id: 2
roleable_id: 2
roleable_type: "<%= role_model_name %>"
<%= role_model_file_name %>_id: 3
admins_to_company:
id: 3
roleable_id: 5
roleable_type: "<%= role_model_name %>"
<%= role_model_file_name %>_id: 4
company_to_admins:
id: 4
roleable_id: 4
roleable_type: "<%= role_model_name %>"
<%= role_model_file_name %>_id: 5
publishers_to_company:
id: 5
roleable_id: 1
roleable_type: "<%= role_model_name %>"
<%= role_model_file_name %>_id: 4

View file

@ -0,0 +1,100 @@
require File.dirname(__FILE__) + '/../spec_helper'
describe "<%= role_membership_model_name %>" do
describe "validations" do
before(:all) do
@<%= role_model_file_name.pluralize %> = []
@<%= role_model_file_name.pluralize %>[0] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>0")
@<%= role_model_file_name.pluralize %>[1] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>1")
@<%= role_model_file_name.pluralize %>[2] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>2")
@<%= role_model_file_name.pluralize %>[3] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>3")
@<%= role_model_file_name.pluralize %>[4] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>4")
@<%= role_model_file_name.pluralize %>[5] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>5")
@<%= role_model_file_name.pluralize %>[6] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>6")
@<%= role_model_file_name.pluralize %>[7] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>7")
@<%= role_model_file_name.pluralize %>[8] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>8")
@<%= role_model_file_name.pluralize %>[9] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>9")
@<%= role_model_file_name.pluralize %>[10] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>10")
@<%= role_model_file_name.pluralize %>[11] = <%= role_model_name %>.new(:name => "<%= role_model_file_name %>11")
@<%= role_model_file_name.pluralize %>.each {|<%= role_model_file_name %>| <%= role_model_file_name %>.save!}
end
before(:each) do
@membership = <%= role_membership_model_name %>.new(:roleable_id => @<%= role_model_file_name.pluralize %>[0].id, :roleable_type => "<%= role_model_name %>", :<%= role_model_file_name %>_id => @<%= role_model_file_name.pluralize %>[1].id)
end
it "should be valid" do
@membership.should be_valid
end
# roleable_id
it "should have a roleable_id" do
@membership.roleable_id = nil
@membership.should_not be_valid
end
it "roleable_id should be an integer" do
@membership.roleable_id = "asd"
@membership.should_not be_valid
end
# roleable_type
it "should have a roleable_type" do
@membership.roleable_type = nil
@membership.should_not be_valid
end
it "roleable_type should be a string" do
@membership.roleable_type = 123
@membership.should_not be_valid
end
it "roleable_type should have a class name format" do
@membership.roleable_type = "asd"
@membership.should_not be_valid
@membership.roleable_type = "User"
@membership.should be_valid
@membership.roleable_type = "Some95WierdClassN4m3"
@membership.should be_valid
end
# <%= role_model_file_name %>_id
it "should have a <%= role_model_file_name %>_id" do
@membership.<%= role_model_file_name %>_id = nil
@membership.should_not be_valid
end
it "<%= role_model_file_name %>_id should be an integer" do
@membership.<%= role_model_file_name %>_id = "asd"
@membership.should_not be_valid
end
it "should not allow a <%= role_model_file_name %> to belong to itself" do
@membership.<%= role_model_file_name %>_id = @<%= role_model_file_name.pluralize %>[0].id
@membership.should_not be_valid
end
# <%= role_model_file_name.pluralize %> cannot belong to each other in a loop
it "should not a allow a <%= role_model_file_name %> to belong to a <%= role_model_file_name %> which belongs to it in a loop" do
@<%= role_model_file_name.pluralize %>[0].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[1]
@<%= role_model_file_name.pluralize %>[1].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[2]
@<%= role_model_file_name.pluralize %>[2].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[3]
@<%= role_model_file_name.pluralize %>[2].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[4]
@<%= role_model_file_name.pluralize %>[2].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[5]
@<%= role_model_file_name.pluralize %>[3].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[6]
@<%= role_model_file_name.pluralize %>[1].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[7]
@<%= role_model_file_name.pluralize %>[3].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[8]
@<%= role_model_file_name.pluralize %>[4].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[9]
@<%= role_model_file_name.pluralize %>[4].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[10]
@<%= role_model_file_name.pluralize %>[5].<%= role_model_file_name.pluralize %> << @<%= role_model_file_name.pluralize %>[11]
@membership3 = <%= role_membership_model_name %>.new(:roleable_id => @<%= role_model_file_name.pluralize %>[11].id, :roleable_type => "<%= role_model_name %>", :<%= role_model_file_name %>_id => @<%= role_model_file_name.pluralize %>[0].id)
@membership3.should_not be_valid
@membership3.errors.full_messages.should include("A <%= role_model_file_name %> cannot belong to a <%= role_model_file_name %> which belongs to it.")
end
after(:all) do
@<%= role_model_file_name.pluralize %>.each {|<%= role_model_file_name %>| <%= role_model_file_name %>.destroy}
end
end
end

View file

@ -0,0 +1,13 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
create_table :<%= role_model_file_name %>s do |t|
t.string :name
<% unless options[:skip_timestamps] %>t.timestamps<% end %>
end
end
def self.down
drop_table :<%= role_model_file_name %>s
end
end

View file

@ -0,0 +1,12 @@
class <%= role_model_name %> < ActiveRecord::Base
has_many :<%= role_membership_model_file_name %>s, :as => :roleable, :dependent => :destroy
has_many :<%= role_model_file_name %>s, :through => :<%= role_membership_model_file_name %>s, :source => :<%= role_model_file_name %>
has_many :roleables, :class_name => "<%= role_membership_model_name %>", :foreign_key => "<%= role_model_file_name %>_id", :dependent => :destroy
has_many :sub<%= role_model_file_name %>s, :through => :roleables, :source => :roleable, :source_type => '<%= role_model_name %>'
#has_many :users, :through => :roleables, :source => :roleable, :source_type => 'User'
validates_uniqueness_of :name
acts_as_permissible
end

View file

@ -0,0 +1,15 @@
publishers:
id: 1
name: "Publishers"
advertisers:
id: 2
name: "Advertisers"
customers:
id: 3
name: "Customers"
company:
id: 4
name: "Company"
admins:
id: 5
name: "Admins"

View file

@ -0,0 +1,53 @@
require File.dirname(__FILE__) + '/../spec_helper'
describe "<%= role_model_name %>" do
describe "validations" do
before(:each) do
@<%= role_model_file_name %> = <%= role_model_name %>.new(:name => "Hunters")
end
it "should be valid" do
@<%= role_model_file_name %>.should be_valid
end
it "should have a unique name" do
@<%= role_model_file_name %>.save
@<%= role_model_file_name %>2 = <%= role_model_name %>.new(:name => "Hunters")
@<%= role_model_file_name %>2.should_not be_valid
end
end
describe "associations" do
fixtures :<%= role_model_file_name %>s, :<%= role_membership_model_file_name %>s
it "should get subgroups correctly" do
<%= role_model_file_name %>s(:company).sub<%= role_model_file_name %>s.size.should == 2
arr = []
arr << <%= role_model_file_name %>s(:publishers)
arr << <%= role_model_file_name %>s(:admins)
<%= role_model_file_name %>s(:company).sub<%= role_model_file_name %>s.should include(arr.first)
<%= role_model_file_name %>s(:company).sub<%= role_model_file_name %>s.should include(arr.last)
<%= role_model_file_name %>s(:customers).sub<%= role_model_file_name %>s.size.should == 2
arr = []
arr << <%= role_model_file_name %>s(:publishers)
arr << <%= role_model_file_name %>s(:advertisers)
<%= role_model_file_name %>s(:customers).sub<%= role_model_file_name %>s.should include(arr.first)
<%= role_model_file_name %>s(:customers).sub<%= role_model_file_name %>s.should include(arr.last)
end
it "should get <%= role_model_file_name %>s correctly" do
<%= role_model_file_name %>s(:publishers).<%= role_model_file_name %>s.size.should == 2
arr = []
arr << <%= role_model_file_name %>s(:customers)
arr << <%= role_model_file_name %>s(:company)
<%= role_model_file_name %>s(:publishers).<%= role_model_file_name %>s.should == arr
<%= role_model_file_name %>s(:admins).<%= role_model_file_name %>s.size.should == 1
arr = []
arr << <%= role_model_file_name %>s(:company)
<%= role_model_file_name %>s(:admins).<%= role_model_file_name %>s.should == arr
end
end
end

View file

@ -0,0 +1,7 @@
# Include hook code here
begin
require "#{Rails.root.to_s}/lib/acts_as_permissible"
ActiveRecord::Base.send(:include, NoamBenAri::Acts::Permissible)
rescue MissingSourceFile => m
end

View file

@ -0,0 +1 @@
puts IO.read(File.join(File.dirname(__FILE__), 'README'))

View file

View file

@ -0,0 +1,4 @@
# desc "Explaining what the task does"
# task :acts_as_permissible do
# # Task goes here
# end

View file

@ -0,0 +1,8 @@
require 'test/unit'
class ActsAsPermissibleTest < Test::Unit::TestCase
# Replace this with your real tests.
def test_this_plugin
flunk
end
end

View file

@ -0,0 +1 @@
# Uninstall hook code here

@ -1 +0,0 @@
Subproject commit 76e4294758db79f52d952aa349b9bcc6482f5db4

9
vendor/plugins/authlogic/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
.DS_Store
.swp
*.log
*.sqlite3
pkg/*
coverage/*
doc/*
benchmarks/*
.specification

345
vendor/plugins/authlogic/CHANGELOG.rdoc vendored Normal file
View file

@ -0,0 +1,345 @@
== 2.1.2
* Return the newly create object for the class level create method, instead of a boolean
* Add a model_name class method for Authlogic::Session for rails 3 compatibility. Will be using ActiveModel eventually, but this should be a quick fix.
== 2.1.1 released 2009-7-04
* Use mb_chars when downcasing the login string to support international characters.
* Check for the existence of the :remember_me key before setting remember_me off of a hash.
* Added check to make sure Authlogic is not loaded too late, causing a NotActivated error.
== 2.1.0 released 2009-6-27
* Fixed bug when using act_like_restful_authentication and setting passwords, needed to add a 2nd parameter to tell if to check against the database or not.
* Don't save record if they are read only.
== 2.0.14 released 2009-6-13
* Fixed issue with using brute force protection AND generalize_credentials_error_messages. Brute force protection was looking to see if there were password errors, which generalize_credentials_error_messages was obfuscating.
* Added db_setup? method to avoid errors during rake tasks where the db might not be set up. Ex: migrations
* Stop using errors.on(key) since that is now deprecated in Rails. Use errors[key] instead.
* Use valid_password? for the method name to validate a password instead of valid_#{password_field}?.
== 2.0.13 released 2009-5-13
* Add authlogic/regex.rb to manifest
== 2.0.12 released 2009-5-13
* Added the ability to add a last_request_update_allowed? method in your controller to pragmatically tell Authlogic when and when not to update the last_request_at field in your database. This only takes effect if the method if present.
* Extracted Authlogic's regular expressions into it's own module to allow easy use of them outside of Authlogic. See Authlogic::Regex for more info.
* Made being_brute_force_protected? true for the Authlogic::Session::BruteForceProtection module.
* Added the configuration option generalize_credentials_error_messages for the Authlogic::Session::Password module. This allows you to generalize your login / password errors messages as to not reveal was the problem was when authenticating. If enabled, when an invalid login is supplied it will use the same exact error message when an invalid password is supplied.
* Update email regular expression to use A-Z0-9 instead of /w as to not allow for diacritical marks in an email address.
* Changed config() convenience method to rw_config() to be more descriptive and less vague.
== 2.0.11 released 2009-4-25
* Fix bug when password is turned off and the SingleAccessToken module calls the after_password_set callback.
* HTTP basic auth can now be toggled on or off. It also checks for the existence of a standard username and password before enabling itself.
* Added option check_passwords_against_database for Authlogic::ActsAsAuthentic::Password to toggle between checking the password against the database value or the object value. Also added the same functionality to the instance method: valid_password?("password", true), where the second argument tells Authlogic to check the password against the database value. The default for this new feature is true.
* Add a maintain_sessions configuration option to Authlogic::ActsAsAuthentic::SessionMaintenance as a "clearer" option to disable automatic session maintenance.
* single_access_allowed_request_types can also be equal to :all instead of just [:all].
* Refactor params_enabled? so that the single_access_allowed? method in controllers takes precedence.
* Added testing comments in the README and expanded on the documentation in Authlogic::TestCase
== 2.0.10 released 2009-4-21
* Mock request is now transparent to non existent methods. Since the methods calls really have no functional value when testing authlogic.
* Allow password confirmation to be disabled.
* Modified login format validation to allow for the + character since emails addresses allow that as a valid character.
* Added merge_* configuration methods for acts_as_authentic to make merging options into configuration options that default to hashes. Just a few convenience methods.
== 2.0.9 released 2009-4-9
* Fixed bug where hooks provided by the password module were called when the password module was not being used due to the fact that the password field did not exist.
* Fixed bug where the find_with_login method was not being aliased if you were using an alternate field besides login.
== 2.0.8 release 2009-4-9
* Dont reset the @password_changed instance variable to false because its halts the callback chain, instead reset it to nil.
== 2.0.7 released 2009-4-9
* Rename TestCase::ControllerAdapter to TestCase::RailsRequestAdapter to help clarify it's usage and fix a constant typo.
== 2.0.6 released 2009-4-9
* Don't use second, use [1] instead so older rails versions don't complain.
* Update email regular expression to be less TLD specific: (?:[A-Z]{2,4}|museum|travel)
* Update shoulda macro for 2.0
* validates_length_of_password_confirmation_field_options defaults to validates_confirmation_of_password_field_options
* Use MockCookieJar in tests instead of a Hash in the MockController.
* Cookies now store the record id as well, for faster lookup. Also to avoid the need to use sessions since sessions are lazily loaded in rails 2.3+
* Add configuration option for Authlogic::ActsAsAuthentic: ignore_blank_passwords
* Fix cookie_domain in rails adapter
* Make password and login fields optional. This allows you to have an alternate authentication method as your main authentication source. Such as OpenID, LDAP, or whatever you want.
* Reset the @password_changed instance variable after the record has been saved.
* Add referer and user_agent to mock requests for testing purposes.
* Add :case_sensitive => false to validates_uniqueness_of calls on the login and email fields.
* MockRequest not tries to use controller.env['REMOTE_ADDR'] for the IP address in tests.
* Add in custom find_with_email and find_with_login methods to perform case insensitive searches for databases that are case sensitive by default. This is only done if the :case_insensitive option for validates_uniqueness_of_login_field_options or validates_uniqueness_of_email_field_options is set to false. Which, as of this version, it is. If you are using MySQL this has been the default behavior all along. If you are using SQLite or Postgres this has NOT been the default behavior.
* Added in exception explaining that you are using the old configuration for acts_as_authentic with an example of the new format.
== 2.0.5 released 2009-3-30
* Stub out authenticate_with_http_basic for TestCase::ControllerAdapter.
* Added second parameter for add_acts_as_authentic module to specify the position: append or prepend.
== 2.0.4 released 2009-3-28
* Added validates_uniqueness_of_login_field_options and validates_uniqueness_of_email_field_options configuration options
* Add in checks to make sure session_class is not nil.
* Cleaned up TestCase some more and added functionality to log users in during functional tests.
== 2.0.3 released 2009-3-26
* Fixed error where default session class does not exist.
* Fixed human_name for the model to use its own human name and not delegate to the associated model. Translation should be under authlogic.models.user_session (or whatever the name of your session is).
* Fixed human_attribute_name to use Authlogic keys for translation instead of ActiveRecord: authlogic.attributes.user_session.login
* For transitioning from restful_authentication, set the REST_AUTH_SITE_KEY to '' if it doesn't exist, instead of nil.
* Completely rewrote Authlogic::Testing, it's now called Authlogic::TestCase. Testing Authlogic is much easier now. Please see Authlogic::TestCase for more info.
== 2.0.2 released 2009-3-24
* Reset failed_login_count if consecutive_failed_logins_limit has been exceed and the failed_login_ban_for has passed.
* Update test helpers to use the new configuration scheme.
* Fixed issue when logging doesn't update last_request_at, so the next persistence try would fail.
== 2.0.1 released 2009-3-23
* Validate length of password.
* Dont save sessions with a ! during session maintenance.
* Add self_and_descendants_from_active_record for Rails 2.3
* Abort acts_as_authentic if there is no DB connection or table.
== 2.0.0 released 2009-3-23
* Refactored nearly all code and tests, especially acts_as_authentic. Got rid of the meta programming and rewrote to use modules and hooks. Also moved all configuration into their related modules.
* Set up a strong API with hooks to allow you to modify behavior and most importantly, easily create "add on" modules or alternate authentication methods, etc.
* Changed configuration method for acts_as_authentic to accept a block instead of a hash.
* The record attribute will NEVER be set until after validation passes, similar to how ActiveRecord executes UPDATEs and CREATEs.
* Fixed bug with session maintenance where user would log in as new user when creating another user account, typically an admin function.
* Brute force protection is only a temporary ban by default, not a permanent one.
* Switched to Hoe for gem management instead of Echoe.
* Added MD5 crypto provider for legacy systems.
* Make password salt field optional for legacy systems.
== 1.4.4 released 2009-3-2
* Moved session maintenance to a before_save, to save on queries executed and to skip an unexpected / additional save on the user object.
* Extracted random string generation into its own class and leverages SecureRandom if it is available
* Move cookies to a higher priority when trying to find the record to help with performance since Rails 3 lazily loads the sessions
* Reset perishable token in a before_save instead of a before_validation
== 1.4.3 released 2009-2-22
* Fixed issue with brute force protection.
== 1.4.2 released 2009-2-20
* Cleaned up callbacks system to use hooks and execute in the proper order.
* Added brute force protection. See the consecutive_failed_logins_limit configuration option in Authlogic::Session::Config. Also see Authlogic::Session:BruteForceProtection
* Fixed issue with calling stale? when there is no record.
* Simon Harris fixed the issue of using lock_version with the associated record and also optimized the library for better performance.
* Implemented saving the record during the callback chain to execute as few queries as possible. This way modules can hook into Authlogic, modify the associated record, and not have to worry about saving the record.
== 1.4.1 released 2009-2-8
* Fixed I18n key misspelling.
* Added I18n keys for ORM error messages.
* Use the password_field configuration value for the alias_methods defined in acts_as_authentic/credentials.rb
* Change shoulda macros implementation to follow the shoulda documentation
* Rails >2.3 uses :domain for the session option instead of :session_domain. Authlogic now uses the proper key in the rails adapter.
* Added validate_password attribute to force password validation regardless if the password is blank. This is useful for forms explicitly changing passwords.
* The class level find method will return a session object if the session is stale. The protection is that there will be no record associated with that session. This allows you to receive an object and call the stale? method on it to determine why the user must log back in.
* Added validate callbacks in Session::Base so you can run callbacks by calling validate :my_method, just like in AR.
* Checked for blank persistence tokens when trying to validate passwords, this is where transitioning occurs. People transitioning from older systems never had a persistence token, which means it would be nil here.
* Update allowed domain name extensions for email
* Ignore default length options for validations if alternate length options are provided, since AR raises an error if 2 different length specifications are provided.
== 1.4.0 released 2009-1-28
* Added support for cookie domain, based on your frameworks session domain configuration
* Updated test helper functions to use the persistence token config value
* Check for UTC times when using Time.now for current_login_at and last_request_at
* Single access now looks for a single_access_allowed? method in your controllers to determine if single access should be allowed or not. Allowing you to define exactly when single access is allowed.
* Finding the authenticated record uses klass.primary_key instead of assuming id.
* BREAKS BACKWARDS COMPATIBILITY: New I18n solution implemented. See Authlogic::I18n for more information.
== 1.3.9 released 2009-1-9
* Added the disable_perishable_token_maintenance option to disable the automatic resetting of the perishable_token, meaning you will have to maintain this yourself.
* Changed shoulda macro to conform to standards so model is not required to be passed
* Modified method definitions for the Session class to check for already defined methods, allowing you to write your own "credential" methods, and Authlogic will not overwrite your custom methods.
* Fixed bug when passing :all to single_access_allowed_request_types
* Added logout_on_timeout configuration option for Session::Base
== 1.3.8 released 2008-12-24
* Only change persistence token if the password is not blank
* Normalize the last_request_at_threshold so that you can pass an integer or a date/time range.
* Fixed bug where password length validations were not being run because the password value was not blank. It should be run if it is a new record, the password has changed, or the password is blank.
* Added disable_magic_states option for sessions, to turn off the automatic checking of "magic states" such as active?, confirmed?, and approved?.
== 1.3.7 released 2008-11-30
* Added session generator: script/generate session UserSession
* Added Test::Unit helpers file, see testing in the README
== 1.3.6 released 2008-11-30
* Modified validates_length_of for password so that there is a fallback validation if the passed "if statement" fails
== 1.3.5 released 2008-11-30
* :transition_from_crypto_provider for acts_as_authentic now accepts an array to transition from multiple providers. Which solves the problem of a double transition.
* Added AES256 as a crypto_provider option, for those that want to use a reversible encryption method by supplying a key.
* Fixed typo for using validates_format_of_options instead of validates_length_of_options
* Fixed bug when accessing the dynamic method for accessing the session record in a namespace, since it uses class_name.underscore which replaces :: with a /
* Added minimum length requirement of 4 for the password, and removed validates_presence_of for password since validates_length_of enforces this
* Set before_validation to reset the persistence token if it is blank, since a password is not required for open id authentication
== 1.3.4 released 2008-11-24
* Delegate human_attribute_name to the ActiveRecord class to take advantage of the I18n feature.
* Fixed issue with passwords from older versions of restful_authentication, the passwords end with --
== 1.3.3 released 2008-11-23
* Updated :act_like_restful_authentication for those using the older version where no site wide key is preset (REST_AUTH_SITE_KEY), Authlogic will adjust automatically based on the presence of this constant.
* Added :transition_from_crypto_provider option for acts_as_authentic to transition your user's passwords to a new algorithm.
* Added :transition_from_restful_authentication for acts_as_authentic to transition your users from restful_authentication to the Authlogic password system. Now you can choose to keep your passwords the same by using :act_like_restful_authentication, which will *NOT* do any transitioning, or you can use :transition_from_crypto_provider which will update your users passwords as they login or new accounts are created, while still allowing users with the old password system to log in.
* Modified the "interface" for the crypto providers to only provide a class level encrypt and matches? method, instead of a class level encrypt and decrypt method.
== 1.3.2 released 2008-11-22
* Updated code to work better with BCrypt, using root level class now.
== 1.3.1 released 2008-11-22
* Fixed typo in acts_as_authentic config when passing the :scope option.
* Added :act_like_restful_authentication option for acts_as_authentic
* Added a new crypto provider: BCrypt, this is for those storing the nuclear launch codes in their apps
== 1.3.0 released 2008-11-21
* BREAKS BACKWARDS COMPATIBILITY: changed the confirm_password field to password_confirmation for acts_as_authentic, since the rails validates_confirmation_of handles creating this attribute and there is no option to change the name of this.
* BREAKS BACKWARDS COMPATIBILITY: Cleaned up all of the validation configuration for acts_as_authentic, as well as the documentation that goes with it, you can accomplish the same things as before, but this is much more flexible and much more organized. This is mainly for those implementing i18n support. Instead of :whatever_message, its now :login_field_validates_length_of_options => {:message => "your i18n friendly message"}. As a side note, with the new i18n support in rails I would not be surprised if this is already done for you since Authlogic uses the ActiveRecord validation methods.
* Got rid of simple delegator for the abstract controller, apparently this has performance issues.
* Cleaned up validations to assume ActiveRecord dirty attributes are present, I think this is a safe assumption.
== 1.2.2 released 2008-11-20
* Added allow_blank_login_and_password_field and allow_blank_email_field options to acts_as_authentic, which allows you to have alternative logins, such as OpenID
* In the session Authlogic now also stores the record id. We use this id to find the record and then check the token against the record, thus allowing for quicker database lookups, while getting the same security.
* Skip validation for reset_perishable_token!
* Added checks for uniqueness validations to only perform if the values have changed, this cuts down on DB queries
* Abstract controller adapter now uses ruby's simple delegator class
* Allow to save with a block: user_session.save { |result| }, result will either be false or self, this is useful when implementing OpenID and other methods
== 1.2.1 released 2008-11-19
* Added build method to authenticates_many association to act like AR association collections.
* Added validation boolean configuration options for acts_as_authentic: validate_field, validate_login_field, validate_password_field, validate_email_field. This turns on and off validations for their respective fields.
* Renamed all password_reset_token terms to perishable_token, including configuration, etc. I still allow for the old configurations so this will not break compatibility, but perishable token is a better name and can be used for account confirmation as well as a password reset token, or anything else you want.
* Renamed all remember_token instances to persistence_token, the term "remember token" doesn't really make sense. I still allow for the old configuration, so this will not break backwards compatibility: persistence_token fits better and makes more sense.
== 1.2.0 released 2008-11-16
* Added check for database set up in acts_as_authentic to prevent errors during migrations.
* Forced logged_in and logged_out named scopes to use seconds.
* Hardened valid_password? method to only allow raw passwords.
* controllers and scopes are no longer stored in class variables but in the Thread.current hash so their instances die out with the thread, which frees up memory.
* Removed single_access_token_field and remember_token_field from Sesson::Config, they are not needed there.
* Added password_reset_token to assist in resetting passwords.
* Added email_field, email_field_regex, email_field_regex_failed_message configuration options to acts_as_authentic. So that you can validate emails as well as a login, instead of the either-or approach.
* Added configuration for all validation messages for the session so that you can modify them and provide I18n support.
== 1.1.1 released 2008-11-13
* Removed ActiveRecord dependency.
* Removed loading shoulda macros by default, moved to shoulda_macros dir.
* Modified how params access works. Added in single_access_token_field which params now uses. See the single access section in the README. Various configuration options added as well.
* Cleaned up acts_as_authentic configuration, added new config module to do this.
* Cleaned up acts_as_authentic tests
* Moved acts_as_authentic sub modules into the proper name spaces
== 1.1.0 released 2008-11-13
* Moved Rack standards into abstract_adapter for the controllers.
* Added authenticating_with_credentials?, authenticating_with_unauthorized_record?
* Fixed typo in abstract_adapter, black to block.
* Cleaned up / reorganized tests.
* Moved ActiveRecord additions to ORM Adapters name space to make way for Data Mapper.
* Reorganized and modified acts_as_authentic to be free standing and not get info from the related session.
* The session now gets its configuration from the model, since determining which fields are present is ORM specific.
* Extracted session and cookie logic into their own modules for Session.
* Moved crypto providers into their own module and added a Sha1 provider to help with the restful_authentication transition.
* Allow the unique_token method to use the alternate crypto_provider if it is a hash algorithm, otherwise default to Sha512.
* 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 required 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.
* Added the option to use session.user, instead of session.record. This is based off of what model your session is authenticating with.
== 1.0.0 released 2008-11-05
* Checked for blank login counts, if a default wasnt set in the migrations.
* Added check for database table in acts_as_authentic to avoid errors in initial setup.
* Completely rewrote tests to be more conventional and thorough tests, removed test_app.
* Modified how validations work so that a validate method was added as well as callbacks for that method.
* Extracted scope support into its own module to help organize code better.
* Added in salt for encryption, just like hashes and removed :crypto_provider_type option for acts_as_authentic.
* Added merb adapters.
* Improved documentation throughout.
== 0.10.4 released 2008-10-31
* Changed configuration to use inheritable attributes
* Cleaned up requires to be in their proper files
* Added in scope support.
== 0.10.3 released 2008-10-31
* Instead of raising an error when extra fields are passed in credentials=, just ignore them.
* Added remember_me config option to set the default value.
* Only call credential methods if an argument was passed.
* More unit tests
* Hardened automatic session updating. Also automatically log the user in if they change their password when logged out.
== 0.10.2 released 2008-10-24
* Added in stretches to the default Sha512 encryption algorithm.
* Use column_names instead of columns when determining if a column is present.
* Improved validation callbacks. after_validation should only be run if valid? = true. Also clear errors before the "before_validation" callback.
== 0.10.1 released 2008-10-24
* Sessions now store the "remember token" instead of the id. This is much safer and guarantees all "sessions" that are logged in are logged in with a valid password. This way stale sessions can't be persisted.
* Bumped security to Sha512 from Sha256.
* Remove attr_protected call in acts_as_authentic
* protected_password should use pasword_field configuration value
* changed magic state "inactive" to "active"
== 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.
* Removed saving_from_session flag, apparently it is not needed.
* Fixed updating sessions to make more sense and be stricter.
* change last_click_at to last_request_at
* Only run "after" callbacks if the result is successful.
== 0.9.0 released 2008-10-24
* Initial release.

20
vendor/plugins/authlogic/LICENSE vendored Normal file
View file

@ -0,0 +1,20 @@
Copyright (c) 2009 Ben Johnson of Binary Logic
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

246
vendor/plugins/authlogic/README.rdoc vendored Normal file
View file

@ -0,0 +1,246 @@
= Authlogic
Authlogic is a clean, simple, and unobtrusive ruby authentication solution.
A code example can replace a thousand words...
Authlogic introduces a new type of model. You can have as many as you want, and name them whatever you want, just like your other models. In this example, we want to authenticate with the User model, which is inferred by the name:
class UserSession < Authlogic::Session::Base
# specify configuration here, such as:
# logout_on_timeout true
# ...many more options in the documentation
end
Log in with any of the following. Create a UserSessionsController and use it just like your other models:
UserSession.create(:login => "bjohnson", :password => "my password", :remember_me => true)
session = UserSession.new(:login => "bjohnson", :password => "my password", :remember_me => true); session.save
UserSession.create(:openid_identifier => "identifier", :remember_me => true) # requires the authlogic-oid "add on" gem
UserSession.create(my_user_object, true) # skip authentication and log the user in directly, the true means "remember me"
The above handles the entire authentication process for you. It first authenticates, then it sets up the proper session values and cookies to persist the session. Just like you would if you rolled your own authentication solution.
You can also log out / destroy the session:
session.destroy
After a session has been created, you can persist it across requests. Thus keeping the user logged in:
session = UserSession.find
To get all of the nice authentication functionality in your model just do this:
class User < ActiveRecord::Base
acts_as_authentic do |c|
c.my_config_option = my_value
end # the configuration block is optional
end
This handles validations, etc. It is also "smart" in the sense that it if a login field is present it will use that to authenticate, if not it will look for an email field, etc. This is all configurable, but for 99% of cases that above is all you will need to do.
Also, sessions are automatically maintained. You can switch this on and off with configuration, but the following will automatically log a user in after a successful registration:
User.create(params[:user])
This also updates the session when the user changes his/her password.
Authlogic is very flexible, it has a strong public API and a plethora of hooks to allow you to modify behavior and extend it. Check out the helpful links below to dig deeper.
== Helpful links
* <b>Documentation:</b> http://rdoc.info/projects/binarylogic/authlogic
* <b>Repository:</b> http://github.com/binarylogic/authlogic/tree/master
* <b>Railscasts Screencast:</b> http://railscasts.com/episodes/160-authlogic
* <b>Live example with OpenID "add on":</b> http://authlogicexample.binarylogic.com
* <b>Live example repository with tutorial in README:</b> http://github.com/binarylogic/authlogic_example/tree/master
* <b>Tutorial: Reset passwords with Authlogic the RESTful way:</b> http://www.binarylogic.com/2008/11/16/tutorial-reset-passwords-with-authlogic
* <b>Issues:</b> http://github.com/binarylogic/authlogic/issues
* <b>Google group:</b> http://groups.google.com/group/authlogic
<b>Before contacting me directly, please read:</b>
If you find a bug or a problem please post it in the issues section. If you need help with something, please use google groups. I check both regularly and get emails when anything happens, so that is the best place to get help. This also benefits other people in the future with the same questions / problems. Thank you.
== Authlogic "add ons"
* <b>Authlogic OpenID addon:</b> http://github.com/binarylogic/authlogic_openid
* <b>Authlogic LDAP addon:</b> http://github.com/binarylogic/authlogic_ldap
* <b>Authlogic Facebook Connect:</b> http://github.com/kalasjocke/authlogic_facebook_connect
* <b>Authlogic OAuth (Twitter):</b> http://github.com/jrallison/authlogic_oauth
* <b>Authlogic PAM:</b> http://github.com/nbudin/authlogic_pam
If you create one of your own, please let me know about it so I can add it to this list. Or just fork the project, add your link, and send me a pull request.
== Session bugs (please read if you are having issues with logging in / out)
Apparently there is a bug with apache / passenger for v2.1.X with sessions not working properly. This is most likely your problem if you are having trouble logging in / out. This is *not* an Authlogic issue. This can be solved by updating passener or using an alternative session store solution, such as active record store.
== Documentation explanation
You can find anything you want about Authlogic in the {documentation}[http://rdoc.info/projects/binarylogic/authlogic], all that you need to do is understand the basic design behind it.
That being said, there are 2 models involved during authentication. Your Authlogic model and your ActiveRecord model:
1. <b>Authlogic::Session</b>, your session models that extend Authlogic::Session::Base.
2. <b>Authlogic::ActsAsAuthentic</b>, which adds in functionality to your ActiveRecord model when you call acts_as_authentic.
Each of the above has its various sub modules that contain common logic. The sub modules are responsible for including *everything* related to it: configuration, class methods, instance methods, etc.
For example, if you want to timeout users after a certain period of inactivity, you would look in <b>Authlogic::Session::Timeout</b>. To help you out, I listed the following publicly relevant modules with short descriptions. For the sake of brevity, there are more modules than listed here, the ones not listed are more for internal use, but you can easily read up on them in the {documentation}[http://rdoc.info/projects/binarylogic/authlogic].
=== Authlogic::ActsAsAuthentic sub modules
These modules are for the ActiveRecord side of things, the models that call acts_as_authentic.
* <b>Authlogic::ActsAsAuthentic::Base</b> - Provides the acts_as_authentic class method and includes all of the submodules.
* <b>Authlogic::ActsAsAuthentic::Email</b> - Handles everything related to the email field.
* <b>Authlogic::ActsAsAuthentic::LoggedInStatus</b> - Provides handy named scopes and methods for determining if the user is logged in or out.
* <b>Authlogic::ActsAsAuthentic::Login</b> - Handles everything related to the login field.
* <b>Authlogic::ActsAsAuthentic::MagicColumns</b> - Handles everything related to the "magic" fields: login_count, failed_login_count, last_request_at, etc.
* <b>Authlogic::ActsAsAuthentic::Password</b> - This one is important. It handles encrypting your password, salting it, etc. It also has support for transitioning password algorithms.
* <b>Authlogic::ActsAsAuthentic::PerishableToken</b> - Handles maintaining the perishable token field, also provides a class level method for finding record using the token.
* <b>Authlogic::ActsAsAuthentic::PersistenceToken</b> - Handles maintaining the persistence token. This is the token stored in cookies and sessions to persist the users session.
* <b>Authlogic::ActsAsAuthentic::RestfulAuthentication</b> - Provides configuration options to easily migrate from the restful_authentication plugin.
* <b>Authlogic::ActsAsAuthentic::SessionMaintenance</b> - Handles automatic session maintenance. EX: a new user registers, automatically log them in. Or a user changes their password, update their session.
* <b>Authlogic::ActsAsAuthentic::SingleAccessToken</b> - Handles maintaining the single access token.
* <b>Authlogic::ActsAsAuthentic::ValidationsScope</b> - Allows you to scope all validations, etc. Just like the :scope option for validates_uniqueness_of
=== Authlogic::Session sub modules
These modules are for the models that extend Authlogic::Session::Base.
* <b>Authlogic::Session::BruteForceProtection</b> - Disables accounts after a certain number of consecutive failed logins attempted.
* <b>Authlogic::Session::Callbacks</b> - Your tools to extend, change, or add onto Authlogic. Lets you hook in and do just about anything you want. Start here if you want to write a plugin or add-on for Authlogic
* <b>Authlogic::Session::Cookies</b> - Authentication via cookies.
* <b>Authlogic::Session::Existence</b> - Creating, saving, and destroying objects.
* <b>Authlogic::Session::HttpAuth</b> - Authentication via basic HTTP authentication.
* <b>Authlogic::Session::Id</b> - Allows sessions to be separated by an id, letting you have multiple sessions for a single user.
* <b>Authlogic::Session::MagicColumns</b> - Maintains "magic" database columns, similar to created_at and updated_at for ActiveRecord.
* <b>Authlogic::Session::MagicStates</b> - Automatically validates based on the records states: active?, approved?, and confirmed?. If those methods exist for the record.
* <b>Authlogic::Session::Params</b> - Authentication via params, aka single access token.
* <b>Authlogic::Session::Password</b> - Authentication via a traditional username and password.
* <b>Authlogic::Session::Persistence</b> - Persisting sessions / finding sessions.
* <b>Authlogic::Session::Session</b> - Authentication via the session, the controller session that is.
* <b>Authlogic::Session::Timeout</b> - Automatically logging out after a certain period of inactivity.
* <b>Authlogic::Session::UnauthorizedRecord</b> - Handles authentication by passing an ActiveRecord object directly.
* <b>Authlogic::Session::Validation</b> - Validation / errors.
=== Miscellaneous modules
Miscellaneous modules that shared across the authentication process and are more "utility" modules and classes.
* <b>Authlogic::AuthenticatesMany</b> - Responsible for allowing you to scope sessions to a parent record. Similar to a has_many and belongs_to relationship. This lets you do the same thing with sessions.
* <b>Authlogic::CryptoProviders</b> - Contains various encryption algorithms that Authlogic uses, allowing you to choose your encryption method.
* <b>Authlogic::I18n</b> - Acts JUST LIKE the rails I18n library, and provides internationalization to Authlogic.
* <b>Authlogic::Random</b> - A simple class to generate random tokens.
* <b>Authlogic::Regex</b> - Contains regular expressions used in Authlogic. Such as those to validate the format of the log or email.
* <b>Authlogic::TestCase</b> - Various helper methods for testing frameworks to help you test your code.
* <b>Authlogic::Version</b> - A handy class for determine the version of Authlogic in a number of ways.
== Quick Rails example
What if creating sessions worked like an ORM library on the surface...
UserSession.create(params[:user_session])
What if your user sessions controller could look just like your other controllers...
class UserSessionsController < ApplicationController
def new
@user_session = UserSession.new
end
def create
@user_session = UserSession.new(params[:user_session])
if @user_session.save
redirect_to account_url
else
render :action => :new
end
end
def destroy
current_user_session.destroy
redirect_to new_user_session_url
end
end
As you can see, this fits nicely into the RESTful development pattern. What about the view...
<% form_for @user_session do |f| %>
<%= f.error_messages %>
<%= f.label :login %><br />
<%= f.text_field :login %><br />
<br />
<%= f.label :password %><br />
<%= f.password_field :password %><br />
<br />
<%= f.submit "Login" %>
<% end %>
Or how about persisting the session...
class ApplicationController
helper_method :current_user_session, :current_user
private
def current_user_session
return @current_user_session if defined?(@current_user_session)
@current_user_session = UserSession.find
end
def current_user
return @current_user if defined?(@current_user)
@current_user = current_user_session && current_user_session.user
end
end
== Install & Use
Install the gem / plugin (recommended)
From rubyforge:
$ sudo gem install authlogic
Or from github:
$ sudo gem install binarylogic-authlogic
Now just add the gem dependency in your projects configuration.
Or you can install this as a plugin:
script/plugin install git://github.com/binarylogic/authlogic.git
== Detailed Setup Tutorial
See the {authlogic example}[http://github.com/binarylogic/authlogic_example/tree/master] for a detailed setup tutorial. I did this because not only do you have a tutorial to go by, but you have an example app that uses the same tutorial, so you can play around with with the code. If you have problems you can compare the code to see what you are doing differently.
== Testing
I think one of the best aspects of Authlogic is testing. For one, it cuts out <b>a lot</b> of redundant tests in your applications because Authlogic is already thoroughly tested for you. It doesn't include a bunch of tests into your application, because it comes tested, just like any other library.
For example, think about ActiveRecord. You don't test the internals of ActiveRecord, because the creators of ActiveRecord have already tested the internals for you. It wouldn't make sense for ActiveRecord to copy it's hundreds of tests into your applications. The same concept applies to Authlogic. You only need to test code you write that is specific to your application, just like everything else in your application.
That being said, testing your code that uses Authlogic is easy. Since everyone uses different testing suites, I created a helpful module called Authlogic::TestCase, which is basically a set of tools for testing code using Authlogic. I explain testing Authlogic thoroughly in the {Authlogic::TestCase section of the documentation}[http://rdoc.info/rdoc/binarylogic/authlogic/blob/f2f6988d3b97e11770b00b72a7a9733df69ffa5b/Authlogic/TestCase.html]. It should answer any questions you have in regards to testing Authlogic.
== Tell me quickly how Authlogic works
Interested in how all of this all works? Think about an ActiveRecord model. A database connection must be established before you can use it. In the case of Authlogic, a controller connection must be established before you can use it. It uses that controller connection to modify cookies, the current session, login with HTTP basic, etc. It connects to the controller through a before filter that is automatically set in your controller which lets Authlogic know about the current controller object. Then Authlogic leverages that to do everything, it's a pretty simple design. Nothing crazy going on, Authlogic is just leveraging the tools your framework provides in the controller object.
== What sets Authlogic apart and why I created it
What inspired me to create Authlogic was the messiness of the current authentication solutions. Put simply, they just didn't feel right, because the logic was not organized properly. As you may know, a common misconception with the MVC design pattern is that the model "M" is only for data access logic, which is wrong. A model is a place for domain logic. This is why the RESTful design pattern and the current authentication solutions don't play nice. Authlogic solves this by placing the session maintenance logic into its own domain (aka "model"). Moving session maintenance into its own domain has its benefits:
1. <b>It's cleaner.</b> There are no generators in Authlogic. Authlogic provides a class that you can use, it's plain and simple ruby. More importantly, the code in your app is code you write, written the way you want, nice and clean. It's code that should be in your app and is specific to your app, not a redundant authentication pattern.
2. <b>Easier to stay up-to-date.</b> To make my point, take a look at the commits to any other authentication solution, then look at the {commits for authlogic}[http://github.com/binarylogic/authlogic/commits/master]. How many commits could you easily start using if you already had an app using that solution? With an alternate solution, very few, if any. All of those cool new features and bug fixes are going to have be manually added or wait for your next application. Which is the main reason a generator is not suitable as an authentication solution. With Authlogic you can start using the latest code with a simple update of a gem. No generators, no mess.
3. <b>It ties everything together on the domain level.</b> Take a new user registration for example, no reason to manually log the user in, authlogic handles this for you via callbacks. The same applies to a user changing their password. Authlogic handles maintaining the session for you.
4. <b>No redundant tests.</b> Because Authlogic doesn't use generators, #1 also applies to tests. Authlogic is *thoroughly* tested for you. You don't go and test the internals of ActiveRecord in each of your apps do you? So why do the same for Authlogic? Your application tests should be for application specific code. Get rid of the noise and make your tests focused and concise, no reason to copy tests from app to app.
5. <b>Framework agnostic</b>. Authlogic can be used in *any* ruby framework you want: Rails, Merb, Sinatra, Mack, your own framework, whatever. It's not tied down to Rails. It does this by abstracting itself from these framework's controllers by using a controller adapter. Thanks to {Rack}[http://rack.rubyforge.org/], there is a defined standard for controller structure, and that's what Authlogic's abstract adapter follows. So if your controller follows the rack standards, you don't need to do anything. Any place it deviates from this is solved by a simple adapter for your framework that closes these gaps. For an example, checkout the Authlogic::ControllerAdapters::MerbAdapter.
5. <b>You are not restricted to a single session.</b> Think about Apple's me.com, where they need you to authenticate a second time before changing your billing information. Why not just create a second session for this? It works just like your initial session. Then your billing controller can require an "ultra secure" session.
6. <b>Easily extendable.</b> One of the distinct advantages of using a library is the ability to use its API, assuming it has one. Authlogic has an *excellent* public API, meaning it can easily be extended and grow beyond the core library. Checkout the "add ons" list above to see what I mean.
Copyright (c) 2009 {Ben Johnson of Binary Logic}[http://www.binarylogic.com], released under the MIT license

41
vendor/plugins/authlogic/Rakefile vendored Normal file
View file

@ -0,0 +1,41 @@
require 'rubygems'
require 'rake'
begin
require 'jeweler'
Jeweler::Tasks.new do |gem|
gem.name = "authlogic"
gem.summary = "A clean, simple, and unobtrusive ruby authentication solution."
gem.email = "bjohnson@binarylogic.com"
gem.homepage = "http://github.com/binarylogic/authlogic"
gem.authors = ["Ben Johnson of Binary Logic"]
gem.add_dependency "activesupport"
end
Jeweler::GemcutterTasks.new
rescue LoadError
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
end
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
begin
require 'rcov/rcovtask'
Rcov::RcovTask.new do |test|
test.libs << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
rescue LoadError
task :rcov do
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
end
end
task :test => :check_dependencies
task :default => :test

5
vendor/plugins/authlogic/VERSION.yml vendored Normal file
View file

@ -0,0 +1,5 @@
---
:major: 2
:minor: 1
:patch: 6
:build:

View file

@ -0,0 +1,216 @@
# Generated by jeweler
# DO NOT EDIT THIS FILE DIRECTLY
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
# -*- encoding: utf-8 -*-
Gem::Specification.new do |s|
s.name = %q{authlogic}
s.version = "2.1.6"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Ben Johnson of Binary Logic"]
s.date = %q{2010-08-04}
s.email = %q{bjohnson@binarylogic.com}
s.extra_rdoc_files = [
"LICENSE",
"README.rdoc"
]
s.files = [
".gitignore",
"CHANGELOG.rdoc",
"LICENSE",
"README.rdoc",
"Rakefile",
"VERSION.yml",
"authlogic.gemspec",
"generators/session/session_generator.rb",
"generators/session/templates/session.rb",
"init.rb",
"lib/authlogic.rb",
"lib/authlogic/acts_as_authentic/base.rb",
"lib/authlogic/acts_as_authentic/email.rb",
"lib/authlogic/acts_as_authentic/logged_in_status.rb",
"lib/authlogic/acts_as_authentic/login.rb",
"lib/authlogic/acts_as_authentic/magic_columns.rb",
"lib/authlogic/acts_as_authentic/password.rb",
"lib/authlogic/acts_as_authentic/perishable_token.rb",
"lib/authlogic/acts_as_authentic/persistence_token.rb",
"lib/authlogic/acts_as_authentic/restful_authentication.rb",
"lib/authlogic/acts_as_authentic/session_maintenance.rb",
"lib/authlogic/acts_as_authentic/single_access_token.rb",
"lib/authlogic/acts_as_authentic/validations_scope.rb",
"lib/authlogic/authenticates_many/association.rb",
"lib/authlogic/authenticates_many/base.rb",
"lib/authlogic/controller_adapters/abstract_adapter.rb",
"lib/authlogic/controller_adapters/merb_adapter.rb",
"lib/authlogic/controller_adapters/rails_adapter.rb",
"lib/authlogic/controller_adapters/sinatra_adapter.rb",
"lib/authlogic/crypto_providers/aes256.rb",
"lib/authlogic/crypto_providers/bcrypt.rb",
"lib/authlogic/crypto_providers/md5.rb",
"lib/authlogic/crypto_providers/sha1.rb",
"lib/authlogic/crypto_providers/sha256.rb",
"lib/authlogic/crypto_providers/sha512.rb",
"lib/authlogic/crypto_providers/wordpress.rb",
"lib/authlogic/i18n.rb",
"lib/authlogic/i18n/translator.rb",
"lib/authlogic/random.rb",
"lib/authlogic/regex.rb",
"lib/authlogic/session/activation.rb",
"lib/authlogic/session/active_record_trickery.rb",
"lib/authlogic/session/base.rb",
"lib/authlogic/session/brute_force_protection.rb",
"lib/authlogic/session/callbacks.rb",
"lib/authlogic/session/cookies.rb",
"lib/authlogic/session/existence.rb",
"lib/authlogic/session/foundation.rb",
"lib/authlogic/session/http_auth.rb",
"lib/authlogic/session/id.rb",
"lib/authlogic/session/klass.rb",
"lib/authlogic/session/magic_columns.rb",
"lib/authlogic/session/magic_states.rb",
"lib/authlogic/session/params.rb",
"lib/authlogic/session/password.rb",
"lib/authlogic/session/perishable_token.rb",
"lib/authlogic/session/persistence.rb",
"lib/authlogic/session/priority_record.rb",
"lib/authlogic/session/scopes.rb",
"lib/authlogic/session/session.rb",
"lib/authlogic/session/timeout.rb",
"lib/authlogic/session/unauthorized_record.rb",
"lib/authlogic/session/validation.rb",
"lib/authlogic/test_case.rb",
"lib/authlogic/test_case/mock_controller.rb",
"lib/authlogic/test_case/mock_cookie_jar.rb",
"lib/authlogic/test_case/mock_logger.rb",
"lib/authlogic/test_case/mock_request.rb",
"lib/authlogic/test_case/rails_request_adapter.rb",
"rails/init.rb",
"shoulda_macros/authlogic.rb",
"test/acts_as_authentic_test/base_test.rb",
"test/acts_as_authentic_test/email_test.rb",
"test/acts_as_authentic_test/logged_in_status_test.rb",
"test/acts_as_authentic_test/login_test.rb",
"test/acts_as_authentic_test/magic_columns_test.rb",
"test/acts_as_authentic_test/password_test.rb",
"test/acts_as_authentic_test/perishable_token_test.rb",
"test/acts_as_authentic_test/persistence_token_test.rb",
"test/acts_as_authentic_test/restful_authentication_test.rb",
"test/acts_as_authentic_test/session_maintenance_test.rb",
"test/acts_as_authentic_test/single_access_test.rb",
"test/authenticates_many_test.rb",
"test/crypto_provider_test/aes256_test.rb",
"test/crypto_provider_test/bcrypt_test.rb",
"test/crypto_provider_test/sha1_test.rb",
"test/crypto_provider_test/sha256_test.rb",
"test/crypto_provider_test/sha512_test.rb",
"test/fixtures/companies.yml",
"test/fixtures/employees.yml",
"test/fixtures/projects.yml",
"test/fixtures/users.yml",
"test/i18n_test.rb",
"test/libs/affiliate.rb",
"test/libs/company.rb",
"test/libs/employee.rb",
"test/libs/employee_session.rb",
"test/libs/ldaper.rb",
"test/libs/ordered_hash.rb",
"test/libs/project.rb",
"test/libs/user.rb",
"test/libs/user_session.rb",
"test/random_test.rb",
"test/session_test/activation_test.rb",
"test/session_test/active_record_trickery_test.rb",
"test/session_test/brute_force_protection_test.rb",
"test/session_test/callbacks_test.rb",
"test/session_test/cookies_test.rb",
"test/session_test/credentials_test.rb",
"test/session_test/existence_test.rb",
"test/session_test/http_auth_test.rb",
"test/session_test/id_test.rb",
"test/session_test/klass_test.rb",
"test/session_test/magic_columns_test.rb",
"test/session_test/magic_states_test.rb",
"test/session_test/params_test.rb",
"test/session_test/password_test.rb",
"test/session_test/perishability_test.rb",
"test/session_test/persistence_test.rb",
"test/session_test/scopes_test.rb",
"test/session_test/session_test.rb",
"test/session_test/timeout_test.rb",
"test/session_test/unauthorized_record_test.rb",
"test/session_test/validation_test.rb",
"test/test_helper.rb"
]
s.homepage = %q{http://github.com/binarylogic/authlogic}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.7}
s.summary = %q{A clean, simple, and unobtrusive ruby authentication solution.}
s.test_files = [
"test/acts_as_authentic_test/base_test.rb",
"test/acts_as_authentic_test/email_test.rb",
"test/acts_as_authentic_test/logged_in_status_test.rb",
"test/acts_as_authentic_test/login_test.rb",
"test/acts_as_authentic_test/magic_columns_test.rb",
"test/acts_as_authentic_test/password_test.rb",
"test/acts_as_authentic_test/perishable_token_test.rb",
"test/acts_as_authentic_test/persistence_token_test.rb",
"test/acts_as_authentic_test/restful_authentication_test.rb",
"test/acts_as_authentic_test/session_maintenance_test.rb",
"test/acts_as_authentic_test/single_access_test.rb",
"test/authenticates_many_test.rb",
"test/crypto_provider_test/aes256_test.rb",
"test/crypto_provider_test/bcrypt_test.rb",
"test/crypto_provider_test/sha1_test.rb",
"test/crypto_provider_test/sha256_test.rb",
"test/crypto_provider_test/sha512_test.rb",
"test/i18n_test.rb",
"test/libs/affiliate.rb",
"test/libs/company.rb",
"test/libs/employee.rb",
"test/libs/employee_session.rb",
"test/libs/ldaper.rb",
"test/libs/ordered_hash.rb",
"test/libs/project.rb",
"test/libs/user.rb",
"test/libs/user_session.rb",
"test/random_test.rb",
"test/session_test/activation_test.rb",
"test/session_test/active_record_trickery_test.rb",
"test/session_test/brute_force_protection_test.rb",
"test/session_test/callbacks_test.rb",
"test/session_test/cookies_test.rb",
"test/session_test/credentials_test.rb",
"test/session_test/existence_test.rb",
"test/session_test/http_auth_test.rb",
"test/session_test/id_test.rb",
"test/session_test/klass_test.rb",
"test/session_test/magic_columns_test.rb",
"test/session_test/magic_states_test.rb",
"test/session_test/params_test.rb",
"test/session_test/password_test.rb",
"test/session_test/perishability_test.rb",
"test/session_test/persistence_test.rb",
"test/session_test/scopes_test.rb",
"test/session_test/session_test.rb",
"test/session_test/timeout_test.rb",
"test/session_test/unauthorized_record_test.rb",
"test/session_test/validation_test.rb",
"test/test_helper.rb"
]
if s.respond_to? :specification_version then
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<activesupport>, [">= 0"])
else
s.add_dependency(%q<activesupport>, [">= 0"])
end
else
s.add_dependency(%q<activesupport>, [">= 0"])
end
end

View file

@ -0,0 +1,9 @@
class SessionGenerator < Rails::Generator::NamedBase
def manifest
record do |m|
m.class_collisions class_name
m.directory File.join('app/models', class_path)
m.template 'session.rb', File.join('app/models', class_path, "#{file_name}.rb")
end
end
end

View file

@ -0,0 +1,2 @@
class <%= class_name %> < Authlogic::Session::Base
end

1
vendor/plugins/authlogic/init.rb vendored Normal file
View file

@ -0,0 +1 @@
require "authlogic"

View file

@ -0,0 +1,64 @@
require "active_record"
AUTHLOGIC_PATH = File.dirname(__FILE__) + "/authlogic/"
[
"i18n",
"random",
"regex",
"controller_adapters/abstract_adapter",
"crypto_providers/md5",
"crypto_providers/sha1",
"crypto_providers/sha256",
"crypto_providers/sha512",
"crypto_providers/bcrypt",
"crypto_providers/aes256",
"authenticates_many/base",
"authenticates_many/association",
"acts_as_authentic/email",
"acts_as_authentic/logged_in_status",
"acts_as_authentic/login",
"acts_as_authentic/magic_columns",
"acts_as_authentic/password",
"acts_as_authentic/perishable_token",
"acts_as_authentic/persistence_token",
"acts_as_authentic/restful_authentication",
"acts_as_authentic/session_maintenance",
"acts_as_authentic/single_access_token",
"acts_as_authentic/validations_scope",
"acts_as_authentic/base",
"session/activation",
"session/active_record_trickery",
"session/brute_force_protection",
"session/callbacks",
"session/cookies",
"session/existence",
"session/foundation",
"session/http_auth",
"session/id",
"session/klass",
"session/magic_columns",
"session/magic_states",
"session/params",
"session/password",
"session/perishable_token",
"session/persistence",
"session/priority_record",
"session/scopes",
"session/session",
"session/timeout",
"session/unauthorized_record",
"session/validation",
"session/base"
].each do |library|
require AUTHLOGIC_PATH + library
end
require AUTHLOGIC_PATH + "controller_adapters/rails_adapter" if defined?( Rails )
require AUTHLOGIC_PATH + "controller_adapters/merb_adapter" if defined?( Merb )
require AUTHLOGIC_PATH + "controller_adapters/sinatra_adapter" if defined?( Sinatra )

View file

@ -0,0 +1,107 @@
module Authlogic
module ActsAsAuthentic
# Provides the base functionality for acts_as_authentic
module Base
def self.included(klass)
klass.class_eval do
extend Config
end
end
module Config
# This includes a lot of helpful methods for authenticating records which The Authlogic::Session module relies on.
# To use it just do:
#
# class User < ActiveRecord::Base
# acts_as_authentic
# end
#
# Configuration is easy:
#
# acts_as_authentic do |c|
# c.my_configuration_option = my_value
# end
#
# See the various sub modules for the configuration they provide.
def acts_as_authentic(unsupported_options = nil, &block)
# Stop all configuration if the DB is not set up
return if !db_setup?
raise ArgumentError.new("You are using the old v1.X.X configuration method for Authlogic. Instead of " +
"passing a hash of configuration options to acts_as_authentic, pass a block: acts_as_authentic { |c| c.my_option = my_value }") if !unsupported_options.nil?
yield self if block_given?
acts_as_authentic_modules.each { |mod| include mod }
end
# Since this part of Authlogic deals with another class, ActiveRecord, we can't just start including things
# in ActiveRecord itself. A lot of these module includes need to be triggered by the acts_as_authentic method
# call. For example, you don't want to start adding in email validations and what not into a model that has
# nothing to do with Authlogic.
#
# That being said, this is your tool for extending Authlogic and "hooking" into the acts_as_authentic call.
def add_acts_as_authentic_module(mod, action = :append)
modules = acts_as_authentic_modules
case action
when :append
modules << mod
when :prepend
modules = [mod] + modules
end
modules.uniq!
write_inheritable_attribute(:acts_as_authentic_modules, modules)
end
# This is the same as add_acts_as_authentic_module, except that it removes the module from the list.
def remove_acts_as_authentic_module(mod)
acts_as_authentic_modules.delete(mod)
acts_as_authentic_modules
end
private
def acts_as_authentic_modules
key = :acts_as_authentic_modules
inheritable_attributes.include?(key) ? read_inheritable_attribute(key) : []
end
def db_setup?
begin
column_names
true
rescue Exception
false
end
end
def rw_config(key, value, default_value = nil, read_value = nil)
if value == read_value
inheritable_attributes.include?(key) ? read_inheritable_attribute(key) : default_value
else
write_inheritable_attribute(key, value)
end
end
def first_column_to_exist(*columns_to_check)
if db_setup?
columns_to_check.each { |column_name| return column_name.to_sym if column_names.include?(column_name.to_s) }
end
columns_to_check.first && columns_to_check.first.to_sym
end
end
end
end
end
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::Base
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::Email
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::LoggedInStatus
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::Login
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::MagicColumns
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::Password
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::PerishableToken
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::PersistenceToken
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::RestfulAuthentication
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::SessionMaintenance
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::SingleAccessToken
::ActiveRecord::Base.send :include, Authlogic::ActsAsAuthentic::ValidationsScope

View file

@ -0,0 +1,110 @@
module Authlogic
module ActsAsAuthentic
# Sometimes models won't have an explicit "login" or "username" field. Instead they want to use the email field.
# In this case, authlogic provides validations to make sure the email submited is actually a valid email. Don't worry,
# if you do have a login or username field, Authlogic will still validate your email field. One less thing you have to
# worry about.
module Email
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Methods)
end
end
# Configuration to modify how Authlogic handles the email field.
module Config
# The name of the field that stores email addresses.
#
# * <tt>Default:</tt> :email, if it exists
# * <tt>Accepts:</tt> Symbol
def email_field(value = nil)
rw_config(:email_field, value, first_column_to_exist(nil, :email, :email_address))
end
alias_method :email_field=, :email_field
# Toggles validating the email field or not.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def validate_email_field(value = nil)
rw_config(:validate_email_field, value, true)
end
alias_method :validate_email_field=, :validate_email_field
# A hash of options for the validates_length_of call for the email field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_length_of_email_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:within => 6..100}
# * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
def validates_length_of_email_field_options(value = nil)
rw_config(:validates_length_of_email_field_options, value, {:within => 6..100})
end
alias_method :validates_length_of_email_field_options=, :validates_length_of_email_field_options
# A convenience function to merge options into the validates_length_of_email_field_options. So intead of:
#
# self.validates_length_of_email_field_options = validates_length_of_email_field_options.merge(:my_option => my_value)
#
# You can do this:
#
# merge_validates_length_of_email_field_options :my_option => my_value
def merge_validates_length_of_email_field_options(options = {})
self.validates_length_of_email_field_options = validates_length_of_email_field_options.merge(options)
end
# A hash of options for the validates_format_of call for the email field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_format_of_email_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:with => Authlogic::Regex.email, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")}
# * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
def validates_format_of_email_field_options(value = nil)
rw_config(:validates_format_of_email_field_options, value, {:with => Authlogic::Regex.email, :message => I18n.t('error_messages.email_invalid', :default => "should look like an email address.")})
end
alias_method :validates_format_of_email_field_options=, :validates_format_of_email_field_options
# See merge_validates_length_of_email_field_options. The same thing except for validates_format_of_email_field_options.
def merge_validates_format_of_email_field_options(options = {})
self.validates_format_of_email_field_options = validates_format_of_email_field_options.merge(options)
end
# A hash of options for the validates_uniqueness_of call for the email field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_uniqueness_of_email_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{email_field}_changed?".to_sym}
# * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
def validates_uniqueness_of_email_field_options(value = nil)
rw_config(:validates_uniqueness_of_email_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{email_field}_changed?".to_sym})
end
alias_method :validates_uniqueness_of_email_field_options=, :validates_uniqueness_of_email_field_options
# See merge_validates_length_of_email_field_options. The same thing except for validates_uniqueness_of_email_field_options.
def merge_validates_uniqueness_of_email_field_options(options = {})
self.validates_uniqueness_of_email_field_options = validates_uniqueness_of_email_field_options.merge(options)
end
end
# All methods relating to the email field
module Methods
def self.included(klass)
klass.class_eval do
if validate_email_field && email_field
validates_length_of email_field, validates_length_of_email_field_options
validates_format_of email_field, validates_format_of_email_field_options
validates_uniqueness_of email_field, validates_uniqueness_of_email_field_options
end
end
end
end
end
end
end

View file

@ -0,0 +1,60 @@
module Authlogic
module ActsAsAuthentic
# Since web applications are stateless there is not sure fire way to tell if a user is logged in or not,
# from the database perspective. The best way to do this is to provide a "timeout" based on inactivity.
# So if that user is inactive for a certain amount of time we assume they are logged out. That's what this
# module is all about.
module LoggedInStatus
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Methods)
end
end
# All configuration for the logged in status feature set.
module Config
# The timeout to determine when a user is logged in or not.
#
# * <tt>Default:</tt> 10.minutes
# * <tt>Accepts:</tt> Fixnum
def logged_in_timeout(value = nil)
rw_config(:logged_in_timeout, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
end
alias_method :logged_in_timeout=, :logged_in_timeout
end
# All methods for the logged in status feature seat.
module Methods
def self.included(klass)
return if !klass.column_names.include?("last_request_at")
klass.class_eval do
include InstanceMethods
scope :logged_in, lambda { {:conditions => ["last_request_at > ?", logged_in_timeout.seconds.ago]} }
scope :logged_out, lambda { {:conditions => ["last_request_at is NULL or last_request_at <= ?", logged_in_timeout.seconds.ago]} }
end
end
module InstanceMethods
# Returns true if the last_request_at > logged_in_timeout.
def logged_in?
raise "Can not determine the records login state because there is no last_request_at column" if !respond_to?(:last_request_at)
!last_request_at.nil? && last_request_at > logged_in_timeout.seconds.ago
end
# Opposite of logged_in?
def logged_out?
!logged_in?
end
private
def logged_in_timeout
self.class.logged_in_timeout
end
end
end
end
end
end

View file

@ -0,0 +1,141 @@
module Authlogic
module ActsAsAuthentic
# Handles everything related to the login field.
module Login
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Methods)
end
end
# Confguration for the login field.
module Config
# The name of the login field in the database.
#
# * <tt>Default:</tt> :login or :username, if they exist
# * <tt>Accepts:</tt> Symbol
def login_field(value = nil)
rw_config(:login_field, value, first_column_to_exist(nil, :login, :username))
end
alias_method :login_field=, :login_field
# Whether or not the validate the login field
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def validate_login_field(value = nil)
rw_config(:validate_login_field, value, true)
end
alias_method :validate_login_field=, :validate_login_field
# A hash of options for the validates_length_of call for the login field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_length_of_login_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:within => 3..100}
# * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
def validates_length_of_login_field_options(value = nil)
rw_config(:validates_length_of_login_field_options, value, {:within => 3..100})
end
alias_method :validates_length_of_login_field_options=, :validates_length_of_login_field_options
# A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
#
# self.validates_length_of_login_field_options = validates_length_of_login_field_options.merge(:my_option => my_value)
#
# You can do this:
#
# merge_validates_length_of_login_field_options :my_option => my_value
def merge_validates_length_of_login_field_options(options = {})
self.validates_length_of_login_field_options = validates_length_of_login_field_options.merge(options)
end
# A hash of options for the validates_format_of call for the login field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_format_of_login_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:with => Authlogic::Regex.login, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")}
# * <tt>Accepts:</tt> Hash of options accepted by validates_format_of
def validates_format_of_login_field_options(value = nil)
rw_config(:validates_format_of_login_field_options, value, {:with => Authlogic::Regex.login, :message => I18n.t('error_messages.login_invalid', :default => "should use only letters, numbers, spaces, and .-_@ please.")})
end
alias_method :validates_format_of_login_field_options=, :validates_format_of_login_field_options
# See merge_validates_length_of_login_field_options. The same thing, except for validates_format_of_login_field_options
def merge_validates_format_of_login_field_options(options = {})
self.validates_format_of_login_field_options = validates_format_of_login_field_options.merge(options)
end
# A hash of options for the validates_uniqueness_of call for the login field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_format_of_login_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym}
# * <tt>Accepts:</tt> Hash of options accepted by validates_uniqueness_of
def validates_uniqueness_of_login_field_options(value = nil)
rw_config(:validates_uniqueness_of_login_field_options, value, {:case_sensitive => false, :scope => validations_scope, :if => "#{login_field}_changed?".to_sym})
end
alias_method :validates_uniqueness_of_login_field_options=, :validates_uniqueness_of_login_field_options
# See merge_validates_length_of_login_field_options. The same thing, except for validates_uniqueness_of_login_field_options
def merge_validates_uniqueness_of_login_field_options(options = {})
self.validates_uniqueness_of_login_field_options = validates_uniqueness_of_login_field_options.merge(options)
end
# This method allows you to find a record with the given login. If you notice, with ActiveRecord you have the
# validates_uniqueness_of validation function. They give you a :case_sensitive option. I handle this in the same
# manner that they handle that. If you are using the login field and set false for the :case_sensitive option in
# validates_uniqueness_of_login_field_options this method will modify the query to look something like:
#
# first(:conditions => ["LOWER(#{quoted_table_name}.#{login_field}) = ?", login.downcase])
#
# If you don't specify this it calls the good old find_by_* method:
#
# find_by_login(login)
#
# The above also applies for using email as your login, except that you need to set the :case_sensitive in
# validates_uniqueness_of_email_field_options to false.
#
# The only reason I need to do the above is for Postgres and SQLite since they perform case sensitive searches with the
# find_by_* methods.
def find_by_smart_case_login_field(login)
if login_field
find_with_case(login_field, login, validates_uniqueness_of_login_field_options[:case_sensitive] != false)
else
find_with_case(email_field, login, validates_uniqueness_of_email_field_options[:case_sensitive] != false)
end
end
private
def find_with_case(field, value, sensitivity = true)
if sensitivity
send("find_by_#{field}", value)
else
first(:conditions => ["LOWER(#{quoted_table_name}.#{field}) = ?", value.mb_chars.downcase])
end
end
end
# All methods relating to the login field
module Methods
# Adds in various validations, modules, etc.
def self.included(klass)
klass.class_eval do
if validate_login_field && login_field
validates_length_of login_field, validates_length_of_login_field_options
validates_format_of login_field, validates_format_of_login_field_options
validates_uniqueness_of login_field, validates_uniqueness_of_login_field_options
end
end
end
end
end
end
end

View file

@ -0,0 +1,24 @@
module Authlogic
module ActsAsAuthentic
# Magic columns are like ActiveRecord's created_at and updated_at columns. They are "magically" maintained for
# you. Authlogic has the same thing, but these are maintained on the session side. Please see Authlogic::Session::MagicColumns
# for more details. This module merely adds validations for the magic columns if they exist.
module MagicColumns
def self.included(klass)
klass.class_eval do
add_acts_as_authentic_module(Methods)
end
end
# Methods relating to the magic columns
module Methods
def self.included(klass)
klass.class_eval do
validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
validates_numericality_of :failed_login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("failed_login_count")
end
end
end
end
end
end

View file

@ -0,0 +1,355 @@
module Authlogic
module ActsAsAuthentic
# This module has a lot of neat functionality. It is responsible for encrypting your password, salting it, and verifying it.
# It can also help you transition to a new encryption algorithm. See the Config sub module for configuration options.
module Password
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Callbacks)
add_acts_as_authentic_module(Methods)
end
end
# All configuration for the password aspect of acts_as_authentic.
module Config
# The name of the crypted_password field in the database.
#
# * <tt>Default:</tt> :crypted_password, :encrypted_password, :password_hash, or :pw_hash
# * <tt>Accepts:</tt> Symbol
def crypted_password_field(value = nil)
rw_config(:crypted_password_field, value, first_column_to_exist(nil, :crypted_password, :encrypted_password, :password_hash, :pw_hash))
end
alias_method :crypted_password_field=, :crypted_password_field
# The name of the password_salt field in the database.
#
# * <tt>Default:</tt> :password_salt, :pw_salt, :salt, nil if none exist
# * <tt>Accepts:</tt> Symbol
def password_salt_field(value = nil)
rw_config(:password_salt_field, value, first_column_to_exist(nil, :password_salt, :pw_salt, :salt))
end
alias_method :password_salt_field=, :password_salt_field
# Whether or not to require a password confirmation. If you don't want your users to confirm their password
# just set this to false.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def require_password_confirmation(value = nil)
rw_config(:require_password_confirmation, value, true)
end
alias_method :require_password_confirmation=, :require_password_confirmation
# By default passwords are required when a record is new or the crypted_password is blank, but if both of these things
# are met a password is not required. In this case, blank passwords are ignored.
#
# Think about a profile page, where the user can edit all of their information, including changing their password.
# If they do not want to change their password they just leave the fields blank. This will try to set the password to
# a blank value, in which case is incorrect behavior. As such, Authlogic ignores this. But let's say you have a completely
# separate page for resetting passwords, you might not want to ignore blank passwords. If this is the case for you, then
# just set this value to false.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def ignore_blank_passwords(value = nil)
rw_config(:ignore_blank_passwords, value, true)
end
alias_method :ignore_blank_passwords=, :ignore_blank_passwords
# When calling valid_password?("some pass") do you want to check that password against what's in that object or whats in
# the datbase. Take this example:
#
# u = User.first
# u.password = "new pass"
# u.valid_password?("old pass")
#
# Should the last line above return true or false? The record hasn't been saved yet, so most would assume true.
# Other would assume false. So I let you decide by giving you this option.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def check_passwords_against_database(value = nil)
rw_config(:check_passwords_against_database, value, true)
end
alias_method :check_passwords_against_database=, :check_passwords_against_database
# Whether or not to validate the password field.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def validate_password_field(value = nil)
rw_config(:validate_password_field, value, true)
end
alias_method :validate_password_field=, :validate_password_field
# A hash of options for the validates_length_of call for the password field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:minimum => 4, :if => :require_password?}
# * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
def validates_length_of_password_field_options(value = nil)
rw_config(:validates_length_of_password_field_options, value, {:minimum => 4, :if => :require_password?})
end
alias_method :validates_length_of_password_field_options=, :validates_length_of_password_field_options
# A convenience function to merge options into the validates_length_of_login_field_options. So intead of:
#
# self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(:my_option => my_value)
#
# You can do this:
#
# merge_validates_length_of_password_field_options :my_option => my_value
def merge_validates_length_of_password_field_options(options = {})
self.validates_length_of_password_field_options = validates_length_of_password_field_options.merge(options)
end
# A hash of options for the validates_confirmation_of call for the password field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> {:if => :require_password?}
# * <tt>Accepts:</tt> Hash of options accepted by validates_confirmation_of
def validates_confirmation_of_password_field_options(value = nil)
rw_config(:validates_confirmation_of_password_field_options, value, {:if => :require_password?})
end
alias_method :validates_confirmation_of_password_field_options=, :validates_confirmation_of_password_field_options
# See merge_validates_length_of_password_field_options. The same thing, except for validates_confirmation_of_password_field_options
def merge_validates_confirmation_of_password_field_options(options = {})
self.validates_confirmation_of_password_field_options = validates_confirmation_of_password_field_options.merge(options)
end
# A hash of options for the validates_length_of call for the password_confirmation field. Allows you to change this however you want.
#
# <b>Keep in mind this is ruby. I wanted to keep this as flexible as possible, so you can completely replace the hash or
# merge options into it. Checkout the convenience function merge_validates_length_of_password_field_options to merge
# options.</b>
#
# * <tt>Default:</tt> validates_length_of_password_field_options
# * <tt>Accepts:</tt> Hash of options accepted by validates_length_of
def validates_length_of_password_confirmation_field_options(value = nil)
rw_config(:validates_length_of_password_confirmation_field_options, value, validates_length_of_password_field_options)
end
alias_method :validates_length_of_password_confirmation_field_options=, :validates_length_of_password_confirmation_field_options
# See merge_validates_length_of_password_field_options. The same thing, except for validates_length_of_password_confirmation_field_options
def merge_validates_length_of_password_confirmation_field_options(options = {})
self.validates_length_of_password_confirmation_field_options = validates_length_of_password_confirmation_field_options.merge(options)
end
# The class you want to use to encrypt and verify your encrypted passwords. See the Authlogic::CryptoProviders module for more info
# on the available methods and how to create your own.
#
# * <tt>Default:</tt> CryptoProviders::Sha512
# * <tt>Accepts:</tt> Class
def crypto_provider(value = nil)
rw_config(:crypto_provider, value, CryptoProviders::Sha512)
end
alias_method :crypto_provider=, :crypto_provider
# Let's say you originally encrypted your passwords with Sha1. Sha1 is starting to join the party with MD5 and you want to switch
# to something stronger. No problem, just specify your new and improved algorithm with the crypt_provider option and then let
# Authlogic know you are transitioning from Sha1 using this option. Authlogic will take care of everything, including transitioning
# your users to the new algorithm. The next time a user logs in, they will be granted access using the old algorithm and their
# password will be resaved with the new algorithm. All new users will obviously use the new algorithm as well.
#
# Lastly, if you want to transition again, you can pass an array of crypto providers. So you can transition from as many algorithms
# as you want.
#
# * <tt>Default:</tt> nil
# * <tt>Accepts:</tt> Class or Array
def transition_from_crypto_providers(value = nil)
rw_config(:transition_from_crypto_providers, (!value.nil? && [value].flatten.compact) || value, [])
end
alias_method :transition_from_crypto_providers=, :transition_from_crypto_providers
end
# Callbacks / hooks to allow other modules to modify the behavior of this module.
module Callbacks
METHODS = [
"before_password_set", "after_password_set",
"before_password_verification", "after_password_verification"
]
def self.included(klass)
return if klass.crypted_password_field.nil?
klass.define_callbacks *METHODS
# If Rails 3, support the new callback syntax
if klass.send(klass.respond_to?(:singleton_class) ? :singleton_class : :metaclass).method_defined?(:set_callback)
METHODS.each do |method|
klass.class_eval <<-"end_eval", __FILE__, __LINE__
def self.#{method}(*methods, &block)
set_callback :#{method}, *methods, &block
end
end_eval
end
end
end
private
METHODS.each do |method|
class_eval <<-"end_eval", __FILE__, __LINE__
def #{method}
run_callbacks(:#{method}) { |result, object| result == false }
end
end_eval
end
end
# The methods related to the password field.
module Methods
def self.included(klass)
return if klass.crypted_password_field.nil?
klass.class_eval do
include InstanceMethods
if validate_password_field
validates_length_of :password, validates_length_of_password_field_options
if require_password_confirmation
validates_confirmation_of :password, validates_confirmation_of_password_field_options
validates_length_of :password_confirmation, validates_length_of_password_confirmation_field_options
end
end
after_save :reset_password_changed
end
end
module InstanceMethods
# The password
def password
@password
end
# This is a virtual method. Once a password is passed to it, it will create new password salt as well as encrypt
# the password.
def password=(pass)
return if ignore_blank_passwords? && pass.blank?
before_password_set
@password = pass
send("#{password_salt_field}=", Authlogic::Random.friendly_token) if password_salt_field
send("#{crypted_password_field}=", crypto_provider.encrypt(*encrypt_arguments(@password, false, act_like_restful_authentication? ? :restful_authentication : nil)))
@password_changed = true
after_password_set
end
# Accepts a raw password to determine if it is the correct password or not. Notice the second argument. That defaults to the value of
# check_passwords_against_database. See that method for mor information, but basically it just tells Authlogic to check the password
# against the value in the database or the value in the object.
def valid_password?(attempted_password, check_against_database = check_passwords_against_database?)
crypted = check_against_database && send("#{crypted_password_field}_changed?") ? send("#{crypted_password_field}_was") : send(crypted_password_field)
return false if attempted_password.blank? || crypted.blank?
before_password_verification
crypto_providers.each_with_index do |encryptor, index|
# The arguments_type of for the transitioning from restful_authentication
arguments_type = (act_like_restful_authentication? && index == 0) ||
(transition_from_restful_authentication? && index > 0 && encryptor == Authlogic::CryptoProviders::Sha1) ?
:restful_authentication : nil
if encryptor.matches?(crypted, *encrypt_arguments(attempted_password, check_against_database, arguments_type))
transition_password(attempted_password) if transition_password?(index, encryptor, crypted, check_against_database)
after_password_verification
return true
end
end
false
end
# Resets the password to a random friendly token.
def reset_password
friendly_token = Authlogic::Random.friendly_token
self.password = friendly_token
self.password_confirmation = friendly_token
end
alias_method :randomize_password, :reset_password
# Resets the password to a random friendly token and then saves the record.
def reset_password!
reset_password
save_without_session_maintenance(:validate => false)
end
alias_method :randomize_password!, :reset_password!
private
def check_passwords_against_database?
self.class.check_passwords_against_database == true
end
def crypto_providers
[crypto_provider] + transition_from_crypto_providers
end
def encrypt_arguments(raw_password, check_against_database, arguments_type = nil)
salt = nil
salt = (check_against_database && send("#{password_salt_field}_changed?") ? send("#{password_salt_field}_was") : send(password_salt_field)) if password_salt_field
case arguments_type
when :restful_authentication
[REST_AUTH_SITE_KEY, salt, raw_password, REST_AUTH_SITE_KEY].compact
else
[raw_password, salt].compact
end
end
# Determines if we need to tranisiton the password.
# If the index > 0 then we are using an "transition from" crypto provider.
# If the encryptor has a cost and the cost it outdated.
# If we aren't using database values
# If we are using database values, only if the password hasnt change so we don't overwrite any changes
def transition_password?(index, encryptor, crypted, check_against_database)
(index > 0 || (encryptor.respond_to?(:cost_matches?) && !encryptor.cost_matches?(send(crypted_password_field)))) &&
(!check_against_database || !send("#{crypted_password_field}_changed?"))
end
def transition_password(attempted_password)
self.password = attempted_password
save(false)
end
def require_password?
new_record? || password_changed? || send(crypted_password_field).blank?
end
def ignore_blank_passwords?
self.class.ignore_blank_passwords == true
end
def password_changed?
@password_changed == true
end
def reset_password_changed
@password_changed = nil
end
def crypted_password_field
self.class.crypted_password_field
end
def password_salt_field
self.class.password_salt_field
end
def crypto_provider
self.class.crypto_provider
end
def transition_from_crypto_providers
self.class.transition_from_crypto_providers
end
end
end
end
end
end

View file

@ -0,0 +1,105 @@
module Authlogic
module ActsAsAuthentic
# This provides a handy token that is "perishable". Meaning the token is only good for a certain amount of time. This is perfect for
# resetting password, confirming accounts, etc. Typically during these actions you send them this token in via their email. Once they
# use the token and do what they need to do, that token should expire. Don't worry about maintaining this, changing it, or expiring it
# yourself. Authlogic does all of this for you. See the sub modules for all of the tools Authlogic provides to you.
module PerishableToken
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Methods)
end
end
# Change how the perishable token works.
module Config
# When using the find_using_perishable_token method the token can expire. If the token is expired, no
# record will be returned. Use this option to specify how long the token is valid for.
#
# * <tt>Default:</tt> 10.minutes
# * <tt>Accepts:</tt> Fixnum
def perishable_token_valid_for(value = nil)
rw_config(:perishable_token_valid_for, (!value.nil? && value.to_i) || value, 10.minutes.to_i)
end
alias_method :perishable_token_valid_for=, :perishable_token_valid_for
# Authlogic tries to expire and change the perishable token as much as possible, without comprising
# it's purpose. This is for security reasons. If you want to manage it yourself, you can stop
# Authlogic from getting your in way by setting this to true.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def disable_perishable_token_maintenance(value = nil)
rw_config(:disable_perishable_token_maintenance, value, false)
end
alias_method :disable_perishable_token_maintenance=, :disable_perishable_token_maintenance
end
# All methods relating to the perishable token.
module Methods
def self.included(klass)
return if !klass.column_names.include?("perishable_token")
klass.class_eval do
extend ClassMethods
include InstanceMethods
validates_uniqueness_of :perishable_token, :if => :perishable_token_changed?
before_save :reset_perishable_token, :unless => :disable_perishable_token_maintenance?
end
end
# Class level methods for the perishable token
module ClassMethods
# Use this methdo to find a record with a perishable token. This method does 2 things for you:
#
# 1. It ignores blank tokens
# 2. It enforces the perishable_token_valid_for configuration option.
#
# If you want to use a different timeout value, just pass it as the second parameter:
#
# User.find_using_perishable_token(token, 1.hour)
def find_using_perishable_token(token, age = self.perishable_token_valid_for)
return if token.blank?
age = age.to_i
conditions_sql = "perishable_token = ?"
conditions_subs = [token]
if column_names.include?("updated_at") && age > 0
conditions_sql += " and updated_at > ?"
conditions_subs << age.seconds.ago
end
find(:first, :conditions => [conditions_sql, *conditions_subs])
end
# This method will raise ActiveRecord::NotFound if no record is found.
def find_using_perishable_token!(token, age = perishable_token_valid_for)
find_using_perishable_token(token, age) || raise(ActiveRecord::RecordNotFound)
end
end
# Instance level methods for the perishable token.
module InstanceMethods
# Resets the perishable token to a random friendly token.
def reset_perishable_token
self.perishable_token = Random.friendly_token
end
# Same as reset_perishable_token, but then saves the record afterwards.
def reset_perishable_token!
reset_perishable_token
save_without_session_maintenance(:validate => false)
end
# A convenience method based on the disable_perishable_token_maintenance configuration option.
def disable_perishable_token_maintenance?
self.class.disable_perishable_token_maintenance == true
end
end
end
end
end
end

View file

@ -0,0 +1,68 @@
module Authlogic
module ActsAsAuthentic
# Maintains the persistence token, the token responsible for persisting sessions. This token
# gets stores in the session and the cookie.
module PersistenceToken
def self.included(klass)
klass.class_eval do
add_acts_as_authentic_module(Methods)
end
end
# Methods for the persistence token.
module Methods
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
if respond_to?(:after_password_set) && respond_to?(:after_password_verification)
after_password_set :reset_persistence_token
after_password_verification :reset_persistence_token!, :if => :reset_persistence_token?
end
validates_presence_of :persistence_token
validates_uniqueness_of :persistence_token, :if => :persistence_token_changed?
before_validation :reset_persistence_token, :if => :reset_persistence_token?
end
end
# Class level methods for the persistence token.
module ClassMethods
# Resets ALL persistence tokens in the database, which will require all users to reauthenticate.
def forget_all
# Paginate these to save on memory
records = nil
i = 0
begin
records = find(:all, :limit => 50, :offset => i)
records.each { |record| record.forget! }
i += 50
end while !records.blank?
end
end
# Instance level methods for the persistence token.
module InstanceMethods
# Resets the persistence_token field to a random hex value.
def reset_persistence_token
self.persistence_token = Authlogic::Random.hex_token
end
# Same as reset_persistence_token, but then saves the record.
def reset_persistence_token!
reset_persistence_token
save_without_session_maintenance(:validate => false)
end
alias_method :forget!, :reset_persistence_token!
private
def reset_persistence_token?
persistence_token.blank?
end
end
end
end
end
end

View file

@ -0,0 +1,61 @@
module Authlogic
module ActsAsAuthentic
# This module is responsible for transitioning existing applications from the restful_authentication plugin.
module RestfulAuthentication
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
end
end
module Config
# Switching an existing app to Authlogic from restful_authentication? No problem, just set this true and your users won't know
# anything changed. From your database perspective nothing will change at all. Authlogic will continue to encrypt passwords
# just like restful_authentication, so your app won't skip a beat. Although, might consider transitioning your users to a newer
# and stronger algorithm. Checkout the transition_from_restful_authentication option.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def act_like_restful_authentication(value = nil)
r = rw_config(:act_like_restful_authentication, value, false)
set_restful_authentication_config if value
r
end
alias_method :act_like_restful_authentication=, :act_like_restful_authentication
# This works just like act_like_restful_authentication except that it will start transitioning your users to the algorithm you
# specify with the crypto provider option. The next time they log in it will resave their password with the new algorithm
# and any new record will use the new algorithm as well. Make sure to update your users table if you are using the default
# migration since it will set crypted_password and salt columns to a maximum width of 40 characters which is not enough.
def transition_from_restful_authentication(value = nil)
r = rw_config(:transition_from_restful_authentication, value, false)
set_restful_authentication_config if value
r
end
alias_method :transition_from_restful_authentication=, :transition_from_restful_authentication
private
def set_restful_authentication_config
crypto_provider_key = act_like_restful_authentication ? :crypto_provider : :transition_from_crypto_providers
self.send("#{crypto_provider_key}=", CryptoProviders::Sha1)
if !defined?(::REST_AUTH_SITE_KEY) || ::REST_AUTH_SITE_KEY.nil?
class_eval("::REST_AUTH_SITE_KEY = ''") if !defined?(::REST_AUTH_SITE_KEY)
CryptoProviders::Sha1.stretches = 1
end
end
end
module InstanceMethods
private
def act_like_restful_authentication?
self.class.act_like_restful_authentication == true
end
def transition_from_restful_authentication?
self.class.transition_from_restful_authentication == true
end
end
end
end
end

View file

@ -0,0 +1,139 @@
module Authlogic
module ActsAsAuthentic
# This is one of my favorite features that I think is pretty cool. It's things like this that make a library great
# and let you know you are on the right track.
#
# Just to clear up any confusion, Authlogic stores both the record id and the persistence token in the session.
# Why? So stale sessions can not be persisted. It stores the id so it can quickly find the record, and the
# persistence token to ensure no sessions are stale. So if the persistence token changes, the user must log
# back in.
#
# Well, the persistence token changes with the password. What happens if the user changes his own password?
# He shouldn't have to log back in, he's the one that made the change.
#
# That being said, wouldn't it be nice if their session and cookie information was automatically updated?
# Instead of cluttering up your controller with redundant session code. The same thing goes for new
# registrations.
#
# That's what this module is all about. This will automatically maintain the cookie and session values as
# records are saved.
module SessionMaintenance
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Methods)
end
end
module Config
# This is more of a convenience method. In order to turn off automatic maintenance of sessions just
# set this to false, or you can also set the session_ids method to a blank array. Both accomplish
# the same thing. This method is a little clearer in it's intentions though.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def maintain_sessions(value = nil)
rw_config(:maintain_sessions, value, true)
end
alias_method :maintain_sessions=, :maintain_sessions
# As you may know, authlogic sessions can be separate by id (See Authlogic::Session::Base#id). You can
# specify here what session ids you want auto maintained. By default it is the main session, which has
# an id of nil.
#
# * <tt>Default:</tt> [nil]
# * <tt>Accepts:</tt> Array
def session_ids(value = nil)
rw_config(:session_ids, value, [nil])
end
alias_method :session_ids=, :session_ids
# The name of the associated session class. This is inferred by the name of the model.
#
# * <tt>Default:</tt> "#{klass.name}Session".constantize
# * <tt>Accepts:</tt> Class
def session_class(value = nil)
const = "#{base_class.name}Session".constantize rescue nil
rw_config(:session_class, value, const)
end
alias_method :session_class=, :session_class
end
module Methods
def self.included(klass)
klass.class_eval do
before_save :get_session_information, :if => :update_sessions?
before_save :maintain_sessions, :if => :update_sessions?
end
end
# Save the record and skip session maintenance all together.
def save_without_session_maintenance(*args)
self.skip_session_maintenance = true
result = save(*args)
self.skip_session_maintenance = false
result
end
private
def skip_session_maintenance=(value)
@skip_session_maintenance = value
end
def skip_session_maintenance
@skip_session_maintenance ||= false
end
def update_sessions?
!skip_session_maintenance && session_class && session_class.activated? && self.class.maintain_sessions == true && !session_ids.blank? && persistence_token_changed?
end
def get_session_information
# Need to determine if we are completely logged out, or logged in as another user
@_sessions = []
session_ids.each do |session_id|
session = session_class.find(session_id, self)
@_sessions << session if session && session.record
end
end
def maintain_sessions
if @_sessions.empty?
create_session
else
update_sessions
end
end
def create_session
# We only want to automatically login into the first session, since this is the main session. The other sessions are sessions
# that need to be created after logging into the main session.
session_id = session_ids.first
session_class.create(*[self, self, session_id].compact)
return true
end
def update_sessions
# We found sessions above, let's update them with the new info
@_sessions.each do |stale_session|
next if stale_session.record != self
stale_session.unauthorized_record = self
stale_session.save
end
return true
end
def session_ids
self.class.session_ids
end
def session_class
self.class.session_class
end
end
end
end
end

View file

@ -0,0 +1,65 @@
module Authlogic
module ActsAsAuthentic
# This module is responsible for maintaining the single_access token. For more information the single access token and how to use it,
# see the Authlogic::Session::Params module.
module SingleAccessToken
def self.included(klass)
klass.class_eval do
extend Config
add_acts_as_authentic_module(Methods)
end
end
# All configuration for the single_access token aspect of acts_as_authentic.
module Config
# The single access token is used for authentication via URLs, such as a private feed. That being said,
# if the user changes their password, that token probably shouldn't change. If it did, the user would have
# to update all of their URLs. So be default this is option is disabled, if you need it, feel free to turn
# it on.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def change_single_access_token_with_password(value = nil)
rw_config(:change_single_access_token_with_password, value, false)
end
alias_method :change_single_access_token_with_password=, :change_single_access_token_with_password
end
# All method, for the single_access token aspect of acts_as_authentic.
module Methods
def self.included(klass)
return if !klass.column_names.include?("single_access_token")
klass.class_eval do
include InstanceMethods
validates_uniqueness_of :single_access_token, :if => :single_access_token_changed?
before_validation :reset_single_access_token, :if => :reset_single_access_token?
after_password_set(:reset_single_access_token, :if => :change_single_access_token_with_password?) if respond_to?(:after_password_set)
end
end
module InstanceMethods
# Resets the single_access_token to a random friendly token.
def reset_single_access_token
self.single_access_token = Authlogic::Random.friendly_token
end
# same as reset_single_access_token, but then saves the record.
def reset_single_access_token!
reset_single_access_token
save_without_session_maintenance
end
protected
def reset_single_access_token?
single_access_token.blank?
end
def change_single_access_token_with_password?
self.class.change_single_access_token_with_password == true
end
end
end
end
end
end

View file

@ -0,0 +1,32 @@
module Authlogic
module ActsAsAuthentic
# Allows you to scope everything to specific fields.
# See the Config submodule for more info.
# For information on how to scope off of a parent object see Authlogic::AuthenticatesMany
module ValidationsScope
def self.included(klass)
klass.class_eval do
extend Config
end
end
# All configuration for the scope feature.
module Config
# Allows you to scope everything to specific field(s). Works just like validates_uniqueness_of.
# For example, let's say a user belongs to a company, and you want to scope everything to the
# company:
#
# acts_as_authentic do |c|
# c.validations_scope = :company_id
# end
#
# * <tt>Default:</tt> nil
# * <tt>Accepts:</tt> Symbol or Array of symbols
def validations_scope(value = nil)
rw_config(:validations_scope, value)
end
alias_method :validations_scope=, :validations_scope
end
end
end
end

View file

@ -0,0 +1,42 @@
module Authlogic
module AuthenticatesMany
# 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
# # ... etc
#
# You can call all of the class level methods off of an object with a saved scope, so that calling the above methods scopes the user
# sessions down to that specific account. To implement this via ActiveRecord do something like:
#
# class User < ActiveRecord::Base
# authenticates_many :user_sessions
# end
class Association
attr_accessor :klass, :find_options, :id
def initialize(klass, find_options, id)
self.klass = klass
self.find_options = find_options
self.id = id
end
[:create, :create!, :find, :new].each do |method|
class_eval <<-"end_eval", __FILE__, __LINE__
def #{method}(*args)
klass.with_scope(scope_options) do
klass.#{method}(*args)
end
end
end_eval
end
alias_method :build, :new
private
def scope_options
{:find_options => find_options, :id => id}
end
end
end
end

View file

@ -0,0 +1,55 @@
module Authlogic
# This allows you to scope your authentication. For example, let's say all users belong to an account, you want to make sure only users
# that belong to that account can actually login into that account. Simple, just do:
#
# class Account < ActiveRecord::Base
# authenticates_many :user_sessions
# end
#
# Now you can scope sessions just like everything else in ActiveRecord:
#
# @account.user_sessions.new(*args)
# @account.user_sessions.create(*args)
# @account.user_sessions.find(*args)
# # ... etc
#
# Checkout the authenticates_many method for a list of options.
# You may also want to checkout Authlogic::ActsAsAuthentic::Scope to scope your model.
module AuthenticatesMany
module Base
# Allows you set essentially set up a relationship with your sessions. See module definition above for more details.
#
# === Options
#
# * <tt>session_class:</tt> default: "#{name}Session",
# This is the related session class.
#
# * <tt>relationship_name:</tt> default: options[:session_class].klass_name.underscore.pluralize,
# This is the name of the relationship you want to use to scope everything. For example an Account has many Users. There should be a relationship
# called :users that you defined with a has_many. The reason we use the relationship is so you don't have to repeat yourself. The relatonship
# could have all kinds of custom options. So instead of repeating yourself we essentially use the scope that the relationship creates.
#
# * <tt>find_options:</tt> default: nil,
# By default the find options are created from the relationship you specify with :relationship_name. But if you want to override this and
# manually specify find_options you can do it here. Specify options just as you would in ActiveRecord::Base.find.
#
# * <tt>scope_cookies:</tt> default: false
# By the nature of cookies they scope theirself if you are using subdomains to access accounts. If you aren't using subdomains you need to have
# separate cookies for each account, assuming a user is logging into mroe than one account. Authlogic can take care of this for you by
# prefixing the name of the cookie and sessin with the model id. You just need to tell Authlogic to do this by passing this option.
def authenticates_many(name, options = {})
options[:session_class] ||= name.to_s.classify.constantize
options[:relationship_name] ||= options[:session_class].klass_name.underscore.pluralize
class_eval <<-"end_eval", __FILE__, __LINE__
def #{name}
find_options = #{options[:find_options].inspect} || #{options[:relationship_name]}.scope(:find)
find_options.delete_if { |key, value| ![:conditions, :include, :joins].include?(key.to_sym) || value.nil? }
@#{name} ||= Authlogic::AuthenticatesMany::Association.new(#{options[:session_class]}, find_options, #{options[:scope_cookies] ? "self.class.model_name.underscore + '_' + self.send(self.class.primary_key).to_s" : "nil"})
end
end_eval
end
end
::ActiveRecord::Base.extend(Base) if defined?(::ActiveRecord)
end
end

View file

@ -0,0 +1,67 @@
module Authlogic
module ControllerAdapters # :nodoc:
# Allows you to use Authlogic in any framework you want, not just rails. See the RailsAdapter or MerbAdapter
# for an example of how to adapt Authlogic to work with your framework.
class AbstractAdapter
attr_accessor :controller
def initialize(controller)
self.controller = controller
end
def authenticate_with_http_basic(&block)
@auth = Rack::Auth::Basic::Request.new(controller.request.env)
if @auth.provided? and @auth.basic?
block.call(*@auth.credentials)
else
false
end
end
def cookies
controller.cookies
end
def cookie_domain
raise NotImplementedError.new("The cookie_domain method has not been implemented by the controller adapter")
end
def params
controller.params
end
def request
controller.request
end
def request_content_type
request.content_type
end
def session
controller.session
end
def responds_to_single_access_allowed?
controller.respond_to?(:single_access_allowed?, true)
end
def single_access_allowed?
controller.send(:single_access_allowed?)
end
def responds_to_last_request_update_allowed?
controller.respond_to?(:last_request_update_allowed?, true)
end
def last_request_update_allowed?
controller.send(:last_request_update_allowed?)
end
private
def method_missing(id, *args, &block)
controller.send(id, *args, &block)
end
end
end
end

View file

@ -0,0 +1,30 @@
module Authlogic
module ControllerAdapters
# Adapts authlogic to work with merb. The point is to close the gap between what authlogic expects and what the merb controller object
# provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
class MerbAdapter < AbstractAdapter
# Lets Authlogic know about the controller object via a before filter, AKA "activates" authlogic.
module MerbImplementation
def self.included(klass) # :nodoc:
klass.before :activate_authlogic
end
def cookie_domain
Merb::Config[:session_cookie_domain]
end
private
def activate_authlogic
Authlogic::Session::Base.controller = MerbAdapter.new(self)
end
end
end
end
end
# make sure we're running inside Merb
if defined?(Merb::Plugins)
Merb::BootLoader.before_app_loads do
Merb::Controller.send(:include, Authlogic::ControllerAdapters::MerbAdapter::MerbImplementation)
end
end

View file

@ -0,0 +1,48 @@
module Authlogic
module ControllerAdapters
# Adapts authlogic to work with rails. The point is to close the gap between what authlogic expects and what the rails controller object
# provides. Similar to how ActiveRecord has an adapter for MySQL, PostgreSQL, SQLite, etc.
class RailsAdapter < AbstractAdapter
class AuthlogicLoadedTooLateError < StandardError; end
def authenticate_with_http_basic(&block)
controller.authenticate_with_http_basic(&block)
end
def cookies
controller.send(:cookies)
end
def cookie_domain
@cookie_domain_key ||= Rails::VERSION::STRING >= '2.3' ? :domain : :session_domain
controller.request.session_options[@cookie_domain_key]
end
def request_content_type
request.format.to_s
end
# Lets Authlogic know about the controller object via a before filter, AKA "activates" authlogic.
module RailsImplementation
def self.included(klass) # :nodoc:
if defined?(::ApplicationController)
raise AuthlogicLoadedTooLateError.new("Authlogic is trying to prepend a before_filter in ActionController::Base to active itself" +
", the problem is that ApplicationController has already been loaded meaning the before_filter won't get copied into your" +
" application. Generally this is due to another gem or plugin requiring your ApplicationController prematurely, such as" +
" the resource_controller plugin. The solution is to require Authlogic before these other gems / plugins. Please require" +
" authlogic first to get rid of this error.")
end
klass.prepend_before_filter :activate_authlogic
end
private
def activate_authlogic
Authlogic::Session::Base.controller = RailsAdapter.new(self)
end
end
end
end
end
ActionController::Base.send(:include, Authlogic::ControllerAdapters::RailsAdapter::RailsImplementation)

View file

@ -0,0 +1,61 @@
# Authlogic bridge for Sinatra
module Authlogic
module ControllerAdapters
module SinatraAdapter
class Cookies
attr_reader :request, :response
def initialize(request, response)
@request = request
@response = response
end
def delete(key, options = {})
@request.cookies.delete(key)
end
def []=(key, options)
@response.set_cookie(key, options)
end
def method_missing(meth, *args, &block)
@request.cookies.send(meth, *args, &block)
end
end
class Controller
attr_reader :request, :response, :cookies
def initialize(request, response)
@request = request
@cookies = Cookies.new(request, response)
end
def session
env['rack.session']
end
def method_missing(meth, *args, &block)
@request.send meth, *args, &block
end
end
class Adapter < AbstractAdapter
def cookie_domain
env['SERVER_NAME']
end
module Implementation
def self.included(klass)
klass.send :before do
controller = Controller.new(request, response)
Authlogic::Session::Base.controller = Adapter.new(controller)
end
end
end
end
end
end
end
Sinatra::Request.send(:include, Authlogic::ControllerAdapters::SinatraAdapter::Adapter::Implementation)

View file

@ -0,0 +1,43 @@
require "openssl"
module Authlogic
module CryptoProviders
# This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
# In an initializer, or before your application initializes, you should do the following:
#
# Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrably a bunch of random characters"
#
# My final comment is that this is a strong encryption method, but its main weakness is that its reversible. If you do not need to reverse the hash
# then you should consider Sha512 or BCrypt instead.
#
# Keep your key in a safe place, some even say the key should be stored on a separate server.
# This won't hurt performance because the only time it will try and access the key on the separate server is during initialization, which only
# happens once. The reasoning behind this is if someone does compromise your server they won't have the key also. Basically, you don't want to
# store the key with the lock.
class AES256
class << self
attr_writer :key
def encrypt(*tokens)
aes.encrypt
aes.key = @key
[aes.update(tokens.join) + aes.final].pack("m").chomp
end
def matches?(crypted, *tokens)
aes.decrypt
aes.key = @key
(aes.update(crypted.unpack("m").first) + aes.final) == tokens.join
rescue OpenSSL::CipherError
false
end
private
def aes
raise ArgumentError.new("You must provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
@aes ||= OpenSSL::Cipher::Cipher.new("AES-256-ECB")
end
end
end
end
end

View file

@ -0,0 +1,90 @@
begin
require "bcrypt"
rescue LoadError
"sudo gem install bcrypt-ruby"
end
module Authlogic
module CryptoProviders
# For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
# secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
# password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
# of the Sha algorithms. I did some benchmarking to save you some time with your decision:
#
# require "bcrypt"
# require "digest"
# require "benchmark"
#
# Benchmark.bm(18) do |x|
# x.report("BCrypt (cost = 10:") { 100.times { BCrypt::Password.create("mypass", :cost => 10) } }
# x.report("BCrypt (cost = 2:") { 100.times { BCrypt::Password.create("mypass", :cost => 2) } }
# x.report("Sha512:") { 100.times { Digest::SHA512.hexdigest("mypass") } }
# x.report("Sha1:") { 100.times { Digest::SHA1.hexdigest("mypass") } }
# end
#
# user system total real
# BCrypt (cost = 10): 10.780000 0.060000 10.840000 ( 11.100289)
# BCrypt (cost = 2): 0.180000 0.000000 0.180000 ( 0.181914)
# Sha512: 0.000000 0.000000 0.000000 ( 0.000829)
# Sha1: 0.000000 0.000000 0.000000 ( 0.000395)
#
# You can play around with the cost to get that perfect balance between performance and security.
#
# Decided BCrypt is for you? Just insall the bcrypt gem:
#
# gem install bcrypt-ruby
#
# Tell acts_as_authentic to use it:
#
# acts_as_authentic do |c|
# c.crypto_provider = Authlogic::CryptoProviders::BCrypt
# end
#
# You are good to go!
class BCrypt
class << self
# This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
# Set this to whatever you want, play around with it to get that perfect balance between security and performance.
def cost
@cost ||= 10
end
attr_writer :cost
# Creates a BCrypt hash for the password passed.
def encrypt(*tokens)
::BCrypt::Password.create(join_tokens(tokens), :cost => cost)
end
# Does the hash match the tokens? Uses the same tokens that were used to encrypt.
def matches?(hash, *tokens)
hash = new_from_hash(hash)
return false if hash.blank?
hash == join_tokens(tokens)
end
# This method is used as a flag to tell Authlogic to "resave" the password upon a successful login, using the new cost
def cost_matches?(hash)
hash = new_from_hash(hash)
if hash.blank?
false
else
hash.cost == cost
end
end
private
def join_tokens(tokens)
tokens.flatten.join
end
def new_from_hash(hash)
begin
::BCrypt::Password.new(hash)
rescue ::BCrypt::Errors::InvalidHash
return nil
end
end
end
end
end
end

View file

@ -0,0 +1,34 @@
require "digest/md5"
module Authlogic
module CryptoProviders
# This class was made for the users transitioning from md5 based systems.
# I highly discourage using this crypto provider as it superbly inferior
# to your other options.
#
# Please use any other provider offered by Authlogic.
class MD5
class << self
attr_accessor :join_token
# The number of times to loop through the encryption.
def stretches
@stretches ||= 1
end
attr_writer :stretches
# Turns your raw password into a MD5 hash.
def encrypt(*tokens)
digest = tokens.flatten.join(join_token)
stretches.times { digest = Digest::MD5.hexdigest(digest) }
digest
end
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
def matches?(crypted, *tokens)
encrypt(*tokens) == crypted
end
end
end
end
end

View file

@ -0,0 +1,35 @@
require "digest/sha1"
module Authlogic
module CryptoProviders
# This class was made for the users transitioning from restful_authentication. I highly discourage using this
# crypto provider as it inferior to your other options. Please use any other provider offered by Authlogic.
class Sha1
class << self
def join_token
@join_token ||= "--"
end
attr_writer :join_token
# The number of times to loop through the encryption. This is ten because that is what restful_authentication defaults to.
def stretches
@stretches ||= 10
end
attr_writer :stretches
# Turns your raw password into a Sha1 hash.
def encrypt(*tokens)
tokens = tokens.flatten
digest = tokens.shift
stretches.times { digest = Digest::SHA1.hexdigest([digest, *tokens].join(join_token)) }
digest
end
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
def matches?(crypted, *tokens)
encrypt(*tokens) == crypted
end
end
end
end
end

View file

@ -0,0 +1,50 @@
require "digest/sha2"
module Authlogic
# The acts_as_authentic method has a crypto_provider option. This allows you to use any type of encryption you like.
# Just create a class with a class level encrypt and matches? method. See example below.
#
# === Example
#
# class MyAwesomeEncryptionMethod
# def self.encrypt(*tokens)
# # the tokens passed will be an array of objects, what type of object is irrelevant,
# # just do what you need to do with them and return a single encrypted string.
# # for example, you will most likely join all of the objects into a single string and then encrypt that string
# end
#
# def self.matches?(crypted, *tokens)
# # return true if the crypted string matches the tokens.
# # depending on your algorithm you might decrypt the string then compare it to the token, or you might
# # encrypt the tokens and make sure it matches the crypted string, its up to you
# end
# end
module CryptoProviders
# = Sha256
#
# Uses the Sha256 hash algorithm to encrypt passwords.
class Sha256
class << self
attr_accessor :join_token
# The number of times to loop through the encryption. This is ten because that is what restful_authentication defaults to.
def stretches
@stretches ||= 20
end
attr_writer :stretches
# Turns your raw password into a Sha256 hash.
def encrypt(*tokens)
digest = tokens.flatten.join(join_token)
stretches.times { digest = Digest::SHA256.hexdigest(digest) }
digest
end
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
def matches?(crypted, *tokens)
encrypt(*tokens) == crypted
end
end
end
end
end

View file

@ -0,0 +1,50 @@
require "digest/sha2"
module Authlogic
# The acts_as_authentic method has a crypto_provider option. This allows you to use any type of encryption you like.
# Just create a class with a class level encrypt and matches? method. See example below.
#
# === Example
#
# class MyAwesomeEncryptionMethod
# def self.encrypt(*tokens)
# # the tokens passed will be an array of objects, what type of object is irrelevant,
# # just do what you need to do with them and return a single encrypted string.
# # for example, you will most likely join all of the objects into a single string and then encrypt that string
# end
#
# def self.matches?(crypted, *tokens)
# # return true if the crypted string matches the tokens.
# # depending on your algorithm you might decrypt the string then compare it to the token, or you might
# # encrypt the tokens and make sure it matches the crypted string, its up to you
# end
# end
module CryptoProviders
# = Sha512
#
# Uses the Sha512 hash algorithm to encrypt passwords.
class Sha512
class << self
attr_accessor :join_token
# The number of times to loop through the encryption. This is ten because that is what restful_authentication defaults to.
def stretches
@stretches ||= 20
end
attr_writer :stretches
# Turns your raw password into a Sha512 hash.
def encrypt(*tokens)
digest = tokens.flatten.join(join_token)
stretches.times { digest = Digest::SHA512.hexdigest(digest) }
digest
end
# Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
def matches?(crypted, *tokens)
encrypt(*tokens) == crypted
end
end
end
end
end

View file

@ -0,0 +1,43 @@
require 'digest/md5'
module Authlogic
module CryptoProviders
class Wordpress
class << self
ITOA64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
def matches?(crypted, *tokens)
stretches = 1 << ITOA64.index(crypted[3,1])
plain, salt = *tokens
hashed = Digest::MD5.digest(salt+plain)
stretches.times do |i|
hashed = Digest::MD5.digest(hashed+plain)
end
crypted[0,12]+encode_64(hashed, 16) == crypted
end
def encode_64(input, length)
output = ""
i = 0
while i < length
value = input[i]
i+=1
break if value.nil?
output += ITOA64[value & 0x3f, 1]
value |= input[i] << 8 if i < length
output += ITOA64[(value >> 6) & 0x3f, 1]
i+=1
break if i >= length
value |= input[i] << 16 if i < length
output += ITOA64[(value >> 12) & 0x3f,1]
i+=1
break if i >= length
output += ITOA64[(value >> 18) & 0x3f,1]
end
output
end
end
end
end
end

View file

@ -0,0 +1,83 @@
require "authlogic/i18n/translator"
module Authlogic
# This class allows any message in Authlogic to use internationalization. In earlier versions of Authlogic each message was translated via configuration.
# This cluttered up the configuration and cluttered up Authlogic. So all translation has been extracted out into this class. Now all messages pass through
# this class, making it much easier to implement in I18n library / plugin you want. Use this as a layer that sits between Authlogic and whatever I18n
# library you want to use.
#
# By default this uses the rails I18n library, if it exists. If it doesnt exist it just returns the default english message. The Authlogic I18n class
# works EXACTLY like the rails I18n class. This is because the arguments are delegated to this class.
#
# Here is how all messages are translated internally with Authlogic:
#
# Authlogic::I18n.t('error_messages.password_invalid', :default => "is invalid")
#
# If you use a different I18n library just replace the build-in I18n::Translator class with your own. For example:
#
# class MyAuthlogicI18nTranslator
# def translate(key, options = {})
# # you will have key which will be something like: "error_messages.password_invalid"
# # you will also have options[:default], which will be the default english version of the message
# # do whatever you want here with the arguments passed to you.
# end
# end
#
# Authlogic::I18n.translator = MyAuthlogicI18nTranslator.new
#
# That it's! Here is a complete list of the keys that are passed. Just define these however you wish:
#
# authlogic:
# error_messages:
# login_blank: can not be blank
# login_not_found: is not valid
# login_invalid: should use only letters, numbers, spaces, and .-_@ please.
# consecutive_failed_logins_limit_exceeded: Consecutive failed logins limit exceeded, account is disabled.
# email_invalid: should look like an email address.
# password_blank: can not be blank
# password_invalid: is not valid
# not_active: Your account is not active
# not_confirmed: Your account is not confirmed
# not_approved: Your account is not approved
# no_authentication_details: You did not provide any details for authentication.
# models:
# user_session: UserSession (or whatever name you are using)
# attributes:
# user_session: (or whatever name you are using)
# login: login
# email: email
# password: password
# remember_me: remember me
module I18n
@@scope = :authlogic
@@translator = nil
class << self
# Returns the current scope. Defaults to :authlogic
def scope
@@scope
end
# Sets the current scope. Used to set a custom scope.
def scope=(scope)
@@scope = scope
end
# Returns the current translator. Defaults to +Translator+.
def translator
@@translator ||= Translator.new
end
# Sets the current translator. Used to set a custom translator.
def translator=(translator)
@@translator = translator
end
# All message translation is passed to this method. The first argument is the key for the message. The second is options, see the rails I18n library for a list of options used.
def translate(key, options = {})
translator.translate key, { :scope => I18n.scope }.merge(options)
end
alias :t :translate
end
end
end

View file

@ -0,0 +1,15 @@
module Authlogic
module I18n
class Translator
# If the I18n gem is present, calls +I18n.translate+ passing all
# arguments, else returns +options[:default]+.
def translate(key, options = {})
if defined?(::I18n)
::I18n.translate key, options
else
options[:default]
end
end
end
end
end

View file

@ -0,0 +1,33 @@
module Authlogic
# Handles generating random strings. If SecureRandom is installed it will default to this and use it instead. SecureRandom comes with ActiveSupport.
# So if you are using this in a rails app you should have this library.
module Random
extend self
SecureRandom = (defined?(::SecureRandom) && ::SecureRandom) || (defined?(::ActiveSupport::SecureRandom) && ::ActiveSupport::SecureRandom)
if SecureRandom
def hex_token
SecureRandom.hex(64)
end
def friendly_token
# use base64url as defined by RFC4648
SecureRandom.base64(15).tr('+/=', '').strip.delete("\n")
end
else
def hex_token
Authlogic::CryptoProviders::Sha512.encrypt(Time.now.to_s + (1..10).collect{ rand.to_s }.join)
end
FRIENDLY_CHARS = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
def friendly_token
newpass = ""
1.upto(20) { |i| newpass << FRIENDLY_CHARS[rand(FRIENDLY_CHARS.size-1)] }
newpass
end
end
end
end

View file

@ -0,0 +1,25 @@
module Authlogic
# This is a module the contains regular expressions used throughout Authlogic. The point of extracting
# them out into their own module is to make them easily available to you for other uses. Ex:
#
# validates_format_of :my_email_field, :with => Authlogic::Regex.email
module Regex
# A general email regular expression. It allows top level domains (TLD) to be from 2 - 4 in length, any
# TLD longer than that must be manually specified. The decisions behind this regular expression were made
# by reading this website: http://www.regular-expressions.info/email.html, which is an excellent resource
# for regular expressions.
def self.email
return @email_regex if @email_regex
email_name_regex = '[A-Z0-9_\.%\+\-]+'
domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
domain_tld_regex = '(?:[A-Z]{2,4}|museum|travel)'
@email_regex = /^#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}$/i
end
# A simple regular expression that only allows for letters, numbers, spaces, and .-_@. Just a standard login / username
# regular expression.
def self.login
/\A\w[\w\.+\-_@ ]+$/
end
end
end

View file

@ -0,0 +1,58 @@
module Authlogic
module Session
# Activating Authlogic requires that you pass it an Authlogic::ControllerAdapters::AbstractAdapter object, or a class that extends it.
# This is sort of like a database connection for an ORM library, Authlogic can't do anything until it is "connected" to a controller.
# If you are using a supported framework, Authlogic takes care of this for you.
module Activation
class NotActivatedError < ::StandardError # :nodoc:
def initialize(session)
super("You must activate the Authlogic::Session::Base.controller with a controller object before creating objects")
end
end
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
end
end
module ClassMethods
# Returns true if a controller has 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. In your framework
# environment this is done for you, but if you are using Authlogic outside of your framework, you need to assign a controller
# object to Authlogic via Authlogic::Session::Base.controller = obj. See the controller= method for more information.
def activated?
!controller.nil?
end
# This accepts a controller object wrapped with the Authlogic controller adapter. The controller adapters close the gap
# between the different controllers in each framework. That being said, Authlogic is expecting your object's class to
# extend Authlogic::ControllerAdapters::AbstractAdapter. See Authlogic::ControllerAdapters for more info.
#
# Lastly, this is thread safe.
def controller=(value)
Thread.current[:authlogic_controller] = value
end
# The current controller object
def controller
Thread.current[:authlogic_controller]
end
end
module InstanceMethods
# Making sure we are activated before we start creating objects
def initialize(*args)
raise NotActivatedError.new(self) unless self.class.activated?
super
end
private
def controller
self.class.controller
end
end
end
end
end

View file

@ -0,0 +1,64 @@
module Authlogic
module Session
# Authlogic looks like ActiveRecord, sounds like ActiveRecord, but its not ActiveRecord. That's the goal here.
# This is useful for the various rails helper methods such as form_for, error_messages_for, or any method that
# expects an ActiveRecord object. The point is to disguise the object as an ActiveRecord object so we can take
# advantage of the many ActiveRecord tools.
module ActiveRecordTrickery
def self.included(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
module ClassMethods
# How to name the attributes of Authlogic, works JUST LIKE ActiveRecord, but instead it uses the following
# namespace:
#
# authlogic.attributes.user_session.login
def human_attribute_name(attribute_key_name, options = {})
options[:count] ||= 1
options[:default] ||= attribute_key_name.to_s.humanize
I18n.t("attributes.#{name.underscore}.#{attribute_key_name}", options)
end
# How to name the class, works JUST LIKE ActiveRecord, except it uses the following namespace:
#
# authlogic.models.user_session
def human_name(*args)
I18n.t("models.#{name.underscore}", {:count => 1, :default => name.humanize})
end
# For rails < 2.3, mispelled
def self_and_descendents_from_active_record
[self]
end
# For rails >= 2.3, mispelling fixed
def self_and_descendants_from_active_record
[self]
end
# For rails >= 3.0
def model_name
if defined?(::ActiveModel)
::ActiveModel::Name.new(self)
else
::ActiveSupport::ModelName.new(self.to_s)
end
end
end
module InstanceMethods
# Don't use this yourself, this is to just trick some of the helpers since this is the method it calls.
def new_record?
new_session?
end
# For rails >= 3.0
def to_model
self
end
end
end
end
end

View file

@ -0,0 +1,37 @@
module Authlogic
module Session # :nodoc:
# This is the base class Authlogic, where all modules are included. For information on functiionality see the various
# sub modules.
class Base
include Foundation
include Callbacks
# Included first so that the session resets itself to nil
include Timeout
# Included in a specific order so they are tried in this order when persisting
include Params
include Cookies
include Session
include HttpAuth
# Included in a specific order so magic states gets ran after a record is found
include Password
include UnauthorizedRecord
include MagicStates
include Activation
include ActiveRecordTrickery
include BruteForceProtection
include Existence
include Klass
include MagicColumns
include PerishableToken
include Persistence
include Scopes
include Id
include Validation
include PriorityRecord
end
end
end

View file

@ -0,0 +1,96 @@
module Authlogic
module Session
# A brute force attacks is executed by hammering a login with as many password combinations as possible, until one works. A brute force attacked is
# generally combated with a slow hasing algorithm such as BCrypt. You can increase the cost, which makes the hash generation slower, and ultimately
# increases the time it takes to execute a brute force attack. Just to put this into perspective, if a hacker was to gain access to your server
# and execute a brute force attack locally, meaning there is no network lag, it would probably take decades to complete. Now throw in network lag
# and it would take MUCH longer.
#
# But for those that are extra paranoid and can't get enough protection, why not stop them as soon as you realize something isn't right? That's
# what this module is all about. By default the consecutive_failed_logins_limit configuration option is set to 50, if someone consecutively fails to login
# after 50 attempts their account will be suspended. This is a very liberal number and at this point it should be obvious that something is not right.
# If you wish to lower this number just set the configuration to a lower number:
#
# class UserSession < Authlogic::Session::Base
# consecutive_failed_logins_limit 10
# end
module BruteForceProtection
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
validate :reset_failed_login_count, :if => :reset_failed_login_count?
validate :validate_failed_logins, :if => :being_brute_force_protected?
end
end
# Configuration for the brute force protection feature.
module Config
# To help protect from brute force attacks you can set a limit on the allowed number of consecutive failed logins. By default this is 50, this is a very liberal
# number, and if someone fails to login after 50 tries it should be pretty obvious that it's a machine trying to login in and very likely a brute force attack.
#
# In order to enable this field your model MUST have a failed_login_count (integer) field.
#
# If you don't know what a brute force attack is, it's when a machine tries to login into a system using every combination of character possible. Thus resulting
# in possibly millions of attempts to log into an account.
#
# * <tt>Default:</tt> 50
# * <tt>Accepts:</tt> Integer, set to 0 to disable
def consecutive_failed_logins_limit(value = nil)
rw_config(:consecutive_failed_logins_limit, value, 50)
end
alias_method :consecutive_failed_logins_limit=, :consecutive_failed_logins_limit
# Once the failed logins limit has been exceed, how long do you want to ban the user? This can be a temporary or permanent ban.
#
# * <tt>Default:</tt> 2.hours
# * <tt>Accepts:</tt> Fixnum, set to 0 for permanent ban
def failed_login_ban_for(value = nil)
rw_config(:failed_login_ban_for, (!value.nil? && value) || value, 2.hours.to_i)
end
alias_method :failed_login_ban_for=, :failed_login_ban_for
end
# The methods available for an Authlogic::Session::Base object that make up the brute force protection feature.
module InstanceMethods
# Returns true when the consecutive_failed_logins_limit has been exceeded and is being temporarily banned.
# Notice the word temporary, the user will not be permanently banned unless you choose to do so with configuration.
# By default they will be banned for 2 hours. During that 2 hour period this method will return true.
def being_brute_force_protected?
exceeded_failed_logins_limit? && (failed_login_ban_for <= 0 ||
(attempted_record.respond_to?(:updated_at) && attempted_record.updated_at >= failed_login_ban_for.seconds.ago))
end
private
def exceeded_failed_logins_limit?
!attempted_record.nil? && attempted_record.respond_to?(:failed_login_count) && consecutive_failed_logins_limit > 0 &&
attempted_record.failed_login_count && attempted_record.failed_login_count >= consecutive_failed_logins_limit
end
def reset_failed_login_count?
exceeded_failed_logins_limit? && !being_brute_force_protected?
end
def reset_failed_login_count
attempted_record.failed_login_count = 0
end
def validate_failed_logins
errors.clear # Clear all other error messages, as they are irrelevant at this point and can only provide additional information that is not needed
errors.add(:base, I18n.t(
'error_messages.consecutive_failed_logins_limit_exceeded',
:default => "Consecutive failed logins limit exceeded, account has been" + (failed_login_ban_for == 0 ? "" : " temporarily") + " disabled."
))
end
def consecutive_failed_logins_limit
self.class.consecutive_failed_logins_limit
end
def failed_login_ban_for
self.class.failed_login_ban_for
end
end
end
end
end

View file

@ -0,0 +1,99 @@
module Authlogic
module Session
# Between these callsbacks and the configuration, this is the contract between me and you to safely
# modify Authlogic's behavior. I will do everything I can to make sure these do not change.
#
# Check out the sub modules of Authlogic::Session. They are very concise, clear, and to the point. More
# importantly they use the same API that you would use to extend Authlogic. That being said, they are great
# examples of how to extend Authlogic and add / modify behavior to Authlogic. These modules could easily be pulled out
# into their own plugin and become an "add on" without any change.
#
# Now to the point of this module. Just like in ActiveRecord you have before_save, before_validation, etc.
# You have similar callbacks with Authlogic, see the METHODS constant below. The order of execution is as follows:
#
# before_persisting
# persist
# after_persisting
# [save record if record.changed?]
#
# before_validation
# before_validation_on_create
# before_validation_on_update
# validate
# after_validation_on_update
# after_validation_on_create
# after_validation
# [save record if record.changed?]
#
# before_save
# before_create
# before_update
# after_update
# after_create
# after_save
# [save record if record.changed?]
#
# before_destroy
# [save record if record.changed?]
# destroy
# after_destroy
#
# Notice the "save record if changed?" lines above. This helps with performance. If you need to make
# changes to the associated record, there is no need to save the record, Authlogic will do it for you.
# This allows multiple modules to modify the record and execute as few queries as possible.
#
# **WARNING**: unlike ActiveRecord, these callbacks must be set up on the class level:
#
# class UserSession < Authlogic::Session::Base
# before_validation :my_method
# validate :another_method
# # ..etc
# end
#
# You can NOT define a "before_validation" method, this is bad practice and does not allow Authlogic
# to extend properly with multiple extensions. Please ONLY use the method above.
module Callbacks
METHODS = [
"before_persisting", "persist", "after_persisting",
"before_validation", "before_validation_on_create", "before_validation_on_update", "validate",
"after_validation_on_update", "after_validation_on_create", "after_validation",
"before_save", "before_create", "before_update", "after_update", "after_create", "after_save",
"before_destroy", "after_destroy"
]
def self.included(base) #:nodoc:
base.send :include, ActiveSupport::Callbacks
base.define_callbacks *METHODS
# If Rails 3, support the new callback syntax
if base.send(base.respond_to?(:singleton_class) ? :singleton_class : :metaclass).method_defined?(:set_callback)
METHODS.each do |method|
base.class_eval <<-"end_eval", __FILE__, __LINE__
def self.#{method}(*methods, &block)
set_callback :#{method}, *methods, &block
end
end_eval
end
end
end
private
METHODS.each do |method|
class_eval <<-"end_eval", __FILE__, __LINE__
def #{method}
run_callbacks(:#{method}) { |result, object| result == false }
end
end_eval
end
def persist
run_callbacks(:persist) { |result, object| result == true }
end
def save_record(alternate_record = nil)
r = alternate_record || record
r.save_without_session_maintenance(:validate => false) if r && r.changed? && !r.readonly?
end
end
end
end

View file

@ -0,0 +1,130 @@
module Authlogic
module Session
# Handles all authentication that deals with cookies, such as persisting, saving, and destroying.
module Cookies
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
persist :persist_by_cookie
after_save :save_cookie
after_destroy :destroy_cookie
end
end
# Configuration for the cookie feature set.
module Config
# The name of the cookie or the key in the cookies hash. Be sure and use a unique name. If you have multiple sessions and they use the same cookie it will cause problems.
# Also, if a id is set it will be inserted into the beginning of the string. Exmaple:
#
# session = UserSession.new
# session.cookie_key => "user_credentials"
#
# session = UserSession.new(:super_high_secret)
# session.cookie_key => "super_high_secret_user_credentials"
#
# * <tt>Default:</tt> "#{guessed_klass_name.underscore}_credentials"
# * <tt>Accepts:</tt> String
def cookie_key(value = nil)
rw_config(:cookie_key, value, "#{guessed_klass_name.underscore}_credentials")
end
alias_method :cookie_key=, :cookie_key
# If sessions should be remembered by default or not.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def remember_me(value = nil)
rw_config(:remember_me, value, false)
end
alias_method :remember_me=, :remember_me
# The length of time until the cookie expires.
#
# * <tt>Default:</tt> 3.months
# * <tt>Accepts:</tt> Integer, length of time in seconds, such as 60 or 3.months
def remember_me_for(value = :_read)
rw_config(:remember_me_for, value, 3.months, :_read)
end
alias_method :remember_me_for=, :remember_me_for
end
# The methods available for an Authlogic::Session::Base object that make up the cookie feature set.
module InstanceMethods
# Allows you to set the remember_me option when passing credentials.
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
case values.first
when Hash
self.remember_me = values.first.with_indifferent_access[:remember_me] if values.first.with_indifferent_access.key?(:remember_me)
else
r = values.find { |value| value.is_a?(TrueClass) || value.is_a?(FalseClass) }
self.remember_me = r if !r.nil?
end
end
# Is the cookie going to expire after the session is over, or will it stick around?
def remember_me
return @remember_me if defined?(@remember_me)
@remember_me = self.class.remember_me
end
# Accepts a boolean as a flag to remember the session or not. Basically to expire the cookie at the end of the session or keep it for "remember_me_until".
def remember_me=(value)
@remember_me = value
end
# See remember_me
def remember_me?
remember_me == true || remember_me == "true" || remember_me == "1"
end
# How long to remember the user if remember_me is true. This is based on the class level configuration: remember_me_for
def remember_me_for
return unless remember_me?
self.class.remember_me_for
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
private
def cookie_key
build_key(self.class.cookie_key)
end
def cookie_credentials
controller.cookies[cookie_key] && controller.cookies[cookie_key].split("::")
end
# Tries to validate the session from information in the cookie
def persist_by_cookie
persistence_token, record_id = cookie_credentials
if !persistence_token.nil?
record = record_id.nil? ? search_for_record("find_by_persistence_token", persistence_token) : search_for_record("find_by_#{klass.primary_key}", record_id)
self.unauthorized_record = record if record && record.persistence_token == persistence_token
valid?
else
false
end
end
def save_cookie
controller.cookies[cookie_key] = {
:value => "#{record.persistence_token}::#{record.send(record.class.primary_key)}",
:expires => remember_me_until,
:domain => controller.cookie_domain
}
end
def destroy_cookie
controller.cookies.delete cookie_key, :domain => controller.cookie_domain
end
end
end
end
end

View file

@ -0,0 +1,93 @@
module Authlogic
module Session
# Provides methods to create and destroy objects. Basically controls their "existence".
module Existence
class SessionInvalidError < ::StandardError # :nodoc:
def initialize(session)
super("Your session is invalid and has the following errors: #{session.errors.full_messages.to_sentence}")
end
end
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
attr_accessor :new_session, :record
end
end
module ClassMethods
# A convenince method. The same as:
#
# session = UserSession.new(*args)
# session.save
#
# Instead you can do:
#
# UserSession.create(*args)
def create(*args, &block)
session = new(*args)
session.save(&block)
session
end
# Same as create but calls create!, which raises an exception when validation fails.
def create!(*args)
session = new(*args)
session.save!
session
end
end
module InstanceMethods
# Clears all errors and the associated record, you should call this terminate a session, thus requring
# the user to authenticate again if it is needed.
def destroy
before_destroy
save_record
errors.clear
@record = nil
after_destroy
true
end
# Returns true if the session is new, meaning no action has been taken on it and a successful save
# has not taken place.
def new_session?
new_session != false
end
# After you have specified all of the details for your session you can try to save it. This will
# run validation checks and find the associated record, if all validation passes. If validation
# does not pass, the save will fail and the erorrs will be stored in the errors object.
def save(&block)
result = nil
if valid?
self.record = attempted_record
before_save
new_session? ? before_create : before_update
new_session? ? after_create : after_update
after_save
save_record
self.new_session = false
result = true
else
result = false
end
yield result if block_given?
result
end
# Same as save but raises an exception of validation errors when validation fails
def save!
result = save
raise SessionInvalidError.new(self) unless result
result
end
end
end
end
end

View file

@ -0,0 +1,71 @@
module Authlogic
module Session
# Sort of like an interface, it sets the foundation for the class, such as the required methods. This also allows
# other modules to overwrite methods and call super on them. It's also a place to put "utility" methods used
# throughout Authlogic.
module Foundation
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
end
end
module ClassMethods
private
def rw_config(key, value, default_value = nil, read_value = nil)
if value == read_value
return read_inheritable_attribute(key) if inheritable_attributes.include?(key)
write_inheritable_attribute(key, default_value)
else
write_inheritable_attribute(key, value)
end
end
end
module InstanceMethods
def initialize(*args)
self.credentials = args
end
# The credentials you passed to create your session. See credentials= for more info.
def credentials
[]
end
# Set your credentials before you save your session. You can pass a hash of credentials:
#
# session.credentials = {:login => "my login", :password => "my password", :remember_me => true}
#
# or you can pass an array of objects:
#
# session.credentials = [my_user_object, true]
#
# and if you need to set an id, just pass it last. This value need be the last item in the array you pass, since the id is something that
# you control yourself, it should never be set from a hash or a form. Examples:
#
# session.credentials = [{:login => "my login", :password => "my password", :remember_me => true}, :my_id]
# session.credentials = [my_user_object, true, :my_id]
def credentials=(values)
end
def inspect
"#<#{self.class.name}: #{credentials.blank? ? "no credentials provided" : credentials.inspect}>"
end
def persisted?
!(new_record? || destroyed?)
end
def to_key
new_record? ? nil : [ self.send(self.class.primary_key) ]
end
private
def build_key(last_part)
last_part
end
end
end
end
end

View file

@ -0,0 +1,58 @@
module Authlogic
module Session
# Handles all authentication that deals with basic HTTP auth. Which is authentication built into the HTTP protocol:
#
# http://username:password@whatever.com
#
# Also, if you are not comfortable letting users pass their raw username and password you can always use the single
# access token. See Authlogic::Session::Params for more info.
module HttpAuth
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
persist :persist_by_http_auth, :if => :persist_by_http_auth?
end
end
# Configuration for the HTTP basic auth feature of Authlogic.
module Config
# Do you want to allow your users to log in via HTTP basic auth?
#
# I recommend keeping this enabled. The only time I feel this should be disabled is if you are not comfortable
# having your users provide their raw username and password. Whatever the reason, you can disable it here.
#
# * <tt>Default:</tt> true
# * <tt>Accepts:</tt> Boolean
def allow_http_basic_auth(value = nil)
rw_config(:allow_http_basic_auth, value, true)
end
alias_method :allow_http_basic_auth=, :allow_http_basic_auth
end
# Instance methods for the HTTP basic auth feature of authlogic.
module InstanceMethods
private
def persist_by_http_auth?
allow_http_basic_auth? && login_field && password_field
end
def persist_by_http_auth
controller.authenticate_with_http_basic do |login, password|
if !login.blank? && !password.blank?
send("#{login_field}=", login)
send("#{password_field}=", password)
return valid?
end
end
false
end
def allow_http_basic_auth?
self.class.allow_http_basic_auth == true
end
end
end
end
end

View file

@ -0,0 +1,41 @@
module Authlogic
module Session
# Allows you to separate sessions with an id, ultimately letting you create multiple sessions for the same user.
module Id
def self.included(klass)
klass.class_eval do
attr_writer :id
end
end
# Setting the id if it is passed in the credentials.
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
self.id = values.last if values.last.is_a?(Symbol)
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 sensitive
# 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 during initialization (see initialize for more information), or as an attribute:
#
# session.id = :my_id
#
# Just be sure and set your id before you save your session.
#
# Lastly, to retrieve your session with the id check out the find class method.
def id
@id
end
private
# Used for things like cookie_key, session_key, etc.
def build_key(last_part)
[id, super].compact.join("_")
end
end
end
end

View file

@ -0,0 +1,78 @@
module Authlogic
module Session
# Handles authenticating via a traditional username and password.
module Klass
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
class << self
attr_accessor :configured_klass_methods
end
end
end
module Config
# Lets you change which model to use for authentication.
#
# * <tt>Default:</tt> inferred from the class name. UserSession would automatically try User
# * <tt>Accepts:</tt> an ActiveRecord class
def authenticate_with(klass)
@klass_name = klass.name
@klass = klass
end
alias_method :authenticate_with=, :authenticate_with
# The name of the class that this session is authenticating with. For example, the UserSession class will
# authenticate with the User class unless you specify otherwise in your configuration. See authenticate_with
# for information on how to change this value.
def klass
@klass ||=
if klass_name
klass_name.constantize
else
nil
end
end
# Same as klass, just returns a string instead of the actual constant.
def klass_name
@klass_name ||= guessed_klass_name
end
# The string of the model name class guessed from the actual session class name.
def guessed_klass_name
guessed_name = name.scan(/(.*)Session/)[0]
guessed_name[0] if guessed_name
end
end
module InstanceMethods
# Creating an alias method for the "record" method based on the klass name, so that we can do:
#
# session.user
#
# instead of:
#
# session.record
def initialize(*args)
if !self.class.configured_klass_methods
self.class.send(:alias_method, klass_name.demodulize.underscore.to_sym, :record)
self.class.configured_klass_methods = true
end
super
end
private
def klass
self.class.klass
end
def klass_name
self.class.klass_name
end
end
end
end
end

View file

@ -0,0 +1,95 @@
module Authlogic
module Session
# Just like ActiveRecord has "magic" columns, such as: created_at and updated_at. Authlogic has its own "magic" columns too:
#
# Column name Description
# login_count Increased every time an explicit login is made. This will *NOT* increase if logging in by a session, cookie, or basic http auth
# failed_login_count This increases for each consecutive failed login. See Authlogic::Session::BruteForceProtection and the consecutive_failed_logins_limit config option for more details.
# last_request_at Updates every time the user logs in, either by explicitly logging in, or logging in by cookie, session, or http auth
# current_login_at Updates with the current time when an explicit login is made.
# last_login_at Updates with the value of current_login_at before it is reset.
# current_login_ip Updates with the request remote_ip when an explicit login is made.
# last_login_ip Updates with the value of current_login_ip before it is reset.
module MagicColumns
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
after_persisting :set_last_request_at, :if => :set_last_request_at?
validate :increase_failed_login_count
before_save :update_info
before_save :set_last_request_at, :if => :set_last_request_at?
end
end
# Configuration for the magic columns feature.
module Config
# Every time a session is found the last_request_at field for that record is updatd with the current time, if that field exists.
# If you want to limit how frequent that field is updated specify the threshold here. For example, if your user is making a
# request every 5 seconds, and you feel this is too frequent, and feel a minute is a good threashold. Set this to 1.minute.
# Once a minute has passed in between requests the field will be updated.
#
# * <tt>Default:</tt> 0
# * <tt>Accepts:</tt> integer representing time in seconds
def last_request_at_threshold(value = nil)
rw_config(:last_request_at_threshold, value, 0)
end
alias_method :last_request_at_threshold=, :last_request_at_threshold
end
# The methods available for an Authlogic::Session::Base object that make up the magic columns feature.
module InstanceMethods
private
def increase_failed_login_count
if invalid_password? && attempted_record.respond_to?(:failed_login_count)
attempted_record.failed_login_count ||= 0
attempted_record.failed_login_count += 1
end
end
def update_info
record.login_count = (record.login_count.blank? ? 1 : record.login_count + 1) if record.respond_to?(:login_count)
record.failed_login_count = 0 if record.respond_to?(:failed_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 = klass.default_timezone == :utc ? Time.now.utc : 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
end
# This method lets authlogic know whether it should allow the last_request_at field to be updated
# with the current time (Time.now). One thing to note here is that it also checks for the existence of a
# last_request_update_allowed? method in your controller. This allows you to control this method pragmatically
# in your controller.
#
# For example, what if you had a javascript function that polled the server updating how much time is left in their
# session before it times out. Obviously you would want to ignore this request, because then the user would never time out.
# So you can do something like this in your controller:
#
# def last_request_update_allowed?
# action_name =! "update_session_time_left"
# end
#
# You can do whatever you want with that method.
def set_last_request_at? # :doc:
return false if !record || !klass.column_names.include?("last_request_at")
return controller.last_request_update_allowed? if controller.responds_to_last_request_update_allowed?
record.last_request_at.blank? || last_request_at_threshold.to_i.seconds.ago >= record.last_request_at
end
def set_last_request_at
record.last_request_at = klass.default_timezone == :utc ? Time.now.utc : Time.now
end
def last_request_at_threshold
self.class.last_request_at_threshold
end
end
end
end
end

View file

@ -0,0 +1,59 @@
module Authlogic
module Session
# Authlogic tries to check the state of the record before creating the session. If your record responds to the following methods and any of them return false, validation will fail:
#
# Method name Description
# active? Is the record marked as active?
# approved? Has the record been approved?
# confirmed? Has the record been conirmed?
#
# Authlogic does nothing to define these methods for you, its up to you to define what they mean. If your object responds to these methods Authlogic will use them, otherwise they are ignored.
#
# 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 Authlogic to check your own "state"? No problem, check out the hooks section below. Add in a before_validation to do your own checking. The sky is the limit.
module MagicStates
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
validate :validate_magic_states, :unless => :disable_magic_states?
end
end
# Configuration for the magic states feature.
module Config
# Set this to true if you want to disable the checking of active?, approved?, and confirmed? on your record. This is more or less of a
# convenience feature, since 99% of the time if those methods exist and return false you will not want the user logging in. You could
# easily accomplish this same thing with a before_validation method or other callbacks.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def disable_magic_states(value = nil)
rw_config(:disable_magic_states, value, false)
end
alias_method :disable_magic_states=, :disable_magic_states
end
# The methods available for an Authlogic::Session::Base object that make up the magic states feature.
module InstanceMethods
private
def disable_magic_states?
self.class.disable_magic_states == true
end
def validate_magic_states
return true if attempted_record.nil?
[:active, :approved, :confirmed].each do |required_status|
if attempted_record.respond_to?("#{required_status}?") && !attempted_record.send("#{required_status}?")
errors.add(:base, I18n.t("error_messages.not_#{required_status}", :default => "Your account is not #{required_status}"))
return false
end
end
true
end
end
end
end
end

View file

@ -0,0 +1,101 @@
module Authlogic
module Session
# This module is responsible for authenticating the user via params, which ultimately allows the user to log in using a URL like the following:
#
# https://www.domain.com?user_credentials=4LiXF7FiGUppIPubBPey
#
# Notice the token in the URL, this is a single access token. A single access token is used for single access only, it is not persisted. Meaning the user
# provides it, Authlogic grants them access, and that's it. If they want access again they need to provide the token again. Authlogic will
# *NEVER* try to persist the session after authenticating through this method.
#
# For added security, this token is *ONLY* allowed for RSS and ATOM requests. You can change this with the configuration. You can also define if
# it is allowed dynamically by defining a single_access_allowed? method in your controller. For example:
#
# class UsersController < ApplicationController
# private
# def single_access_allowed?
# action_name == "index"
# end
#
# Also, by default, this token is permanent. Meaning if the user changes their password, this token will remain the same. It will only change
# when it is explicitly reset.
#
# You can modify all of this behavior with the Config sub module.
module Params
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
attr_accessor :single_access
persist :persist_by_params
end
end
# Configuration for the params / single access feature.
module Config
# Works exactly like cookie_key, but for params. So a user can login via params just like a cookie or a session. Your URL would look like:
#
# http://www.domain.com?user_credentials=my_single_access_key
#
# 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. Check out cookie_key for more details. Also checkout the "Single Access / Private Feeds Access" section in the README.
#
# * <tt>Default:</tt> cookie_key
# * <tt>Accepts:</tt> String
def params_key(value = nil)
rw_config(:params_key, value, cookie_key)
end
alias_method :params_key=, :params_key
# Authentication is allowed via a single access token, but maybe this is something you don't want for your application as a whole. Maybe this is
# something you only want for specific request types. Specify a list of allowed request types and single access authentication will only be
# allowed for the ones you specify.
#
# * <tt>Default:</tt> ["application/rss+xml", "application/atom+xml"]
# * <tt>Accepts:</tt> String of a request type, or :all or :any to allow single access authentication for any and all request types
def single_access_allowed_request_types(value = nil)
rw_config(:single_access_allowed_request_types, value, ["application/rss+xml", "application/atom+xml"])
end
alias_method :single_access_allowed_request_types=, :single_access_allowed_request_types
end
# The methods available for an Authlogic::Session::Base object that make up the params / single access feature.
module InstanceMethods
private
def persist_by_params
return false if !params_enabled?
self.unauthorized_record = search_for_record("find_by_single_access_token", params_credentials)
self.single_access = valid?
end
def params_enabled?
return false if !params_credentials || !klass.column_names.include?("single_access_token")
return controller.single_access_allowed? if controller.responds_to_single_access_allowed?
case single_access_allowed_request_types
when Array
single_access_allowed_request_types.include?(controller.request_content_type) || single_access_allowed_request_types.include?(:all)
else
[:all, :any].include?(single_access_allowed_request_types)
end
end
def params_key
build_key(self.class.params_key)
end
def single_access?
single_access == true
end
def single_access_allowed_request_types
self.class.single_access_allowed_request_types
end
def params_credentials
controller.params[params_key]
end
end
end
end
end

View file

@ -0,0 +1,240 @@
module Authlogic
module Session
# Handles authenticating via a traditional username and password.
module Password
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
validate :validate_by_password, :if => :authenticating_with_password?
class << self
attr_accessor :configured_password_methods
end
end
end
# Password configuration
module Config
# Authlogic tries to validate the credentials passed to it. One part of validation is actually finding the user and
# making sure it exists. What method it uses the do this is up to you.
#
# Let's say you have a UserSession that is authenticating a User. By default UserSession will call User.find_by_login(login).
# You can change what method UserSession calls by specifying it here. Then in your User model you can make that method do
# anything you want, giving you complete control of how users are found by the UserSession.
#
# Let's take an example: You want to allow users to login by username or email. Set this to the name of the class method
# that does this in the User model. Let's call it "find_by_username_or_email"
#
# class User < ActiveRecord::Base
# def self.find_by_username_or_email(login)
# find_by_username(login) || find_by_email(login)
# end
# end
#
# Now just specify the name of this method for this configuration option and you are all set. You can do anything you
# want here. Maybe you allow users to have multiple logins and you want to search a has_many relationship, etc. The sky is the limit.
#
# * <tt>Default:</tt> "find_by_smart_case_login_field"
# * <tt>Accepts:</tt> Symbol or String
def find_by_login_method(value = nil)
rw_config(:find_by_login_method, value, "find_by_smart_case_login_field")
end
alias_method :find_by_login_method=, :find_by_login_method
# The text used to identify credentials (username/password) combination when a bad login attempt occurs.
# When you show error messages for a bad login, it's considered good security practice to hide which field
# the user has entered incorrectly (the login field or the password field). For a full explanation, see
# http://www.gnucitizen.org/blog/username-enumeration-vulnerabilities/
#
# Example of use:
#
# class UserSession < Authlogic::Session::Base
# generalize_credentials_error_messages true
# end
#
# This would make the error message for bad logins and bad passwords look identical:
#
# Login/Password combination is not valid
#
# Alternatively you may use a custom message:
#
# class UserSession < AuthLogic::Session::Base
# generalize_credentials_error_messages "Your login information is invalid"
# end
#
# This will instead show your custom error message when the UserSession is invalid.
#
# The downside to enabling this is that is can be too vague for a user that has a hard time remembering
# their username and password combinations. It also disables the ability to to highlight the field
# with the error when you use form_for.
#
# If you are developing an app where security is an extreme priority (such as a financial application),
# then you should enable this. Otherwise, leaving this off is fine.
#
# * <tt>Default</tt> false
# * <tt>Accepts:</tt> Boolean
def generalize_credentials_error_messages(value = nil)
rw_config(:generalize_credentials_error_messages, value, false)
end
alias_method :generalize_credentials_error_messages=, :generalize_credentials_error_messages
# The name of the method you want Authlogic to create for storing the login / username. Keep in mind this is just for your
# Authlogic::Session, if you want it can be something completely different than the field in your model. So if you wanted people to
# login with a field called "login" and then find users by email this is compeltely doable. See the find_by_login_method configuration
# option for more details.
#
# * <tt>Default:</tt> klass.login_field || klass.email_field
# * <tt>Accepts:</tt> Symbol or String
def login_field(value = nil)
rw_config(:login_field, value, klass.login_field || klass.email_field)
end
alias_method :login_field=, :login_field
# Works exactly like login_field, but for the password instead. Returns :password if a login_field exists.
#
# * <tt>Default:</tt> :password
# * <tt>Accepts:</tt> Symbol or String
def password_field(value = nil)
rw_config(:password_field, value, login_field && :password)
end
alias_method :password_field=, :password_field
# The name of the method in your model used to verify the password. This should be an instance method. It should also
# be prepared to accept a raw password and a crytped password.
#
# * <tt>Default:</tt> "valid_password?"
# * <tt>Accepts:</tt> Symbol or String
def verify_password_method(value = nil)
rw_config(:verify_password_method, value, "valid_password?")
end
alias_method :verify_password_method=, :verify_password_method
end
# Password related instance methods
module InstanceMethods
def initialize(*args)
if !self.class.configured_password_methods
if login_field
self.class.send(:attr_writer, login_field) if !respond_to?("#{login_field}=")
self.class.send(:attr_reader, login_field) if !respond_to?(login_field)
end
if password_field
self.class.send(:attr_writer, password_field) if !respond_to?("#{password_field}=")
self.class.send(:define_method, password_field) {} if !respond_to?(password_field)
self.class.class_eval <<-"end_eval", __FILE__, __LINE__
private
# The password should not be accessible publicly. This way forms using form_for don't fill the password with the
# attempted password. To prevent this we just create this method that is private.
def protected_#{password_field}
@#{password_field}
end
end_eval
end
self.class.configured_password_methods = true
end
super
end
# Returns the login_field / password_field credentials combination in hash form.
def credentials
if authenticating_with_password?
details = {}
details[login_field.to_sym] = send(login_field)
details[password_field.to_sym] = "<protected>"
details
else
super
end
end
# Accepts the login_field / password_field credentials combination in hash form.
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
if values.first.is_a?(Hash)
values.first.with_indifferent_access.slice(login_field, password_field).each do |field, value|
next if value.blank?
send("#{field}=", value)
end
end
end
def invalid_password?
invalid_password == true
end
private
def authenticating_with_password?
login_field && (!send(login_field).nil? || !send("protected_#{password_field}").nil?)
end
def validate_by_password
self.invalid_password = false
errors.add(login_field, I18n.t('error_messages.login_blank', :default => "cannot be blank")) if send(login_field).blank?
errors.add(password_field, I18n.t('error_messages.password_blank', :default => "cannot be blank")) if send("protected_#{password_field}").blank?
return if errors.count > 0
self.attempted_record = search_for_record(find_by_login_method, send(login_field))
if attempted_record.blank?
generalize_credentials_error_messages? ?
add_general_credentials_error :
errors.add(login_field, I18n.t('error_messages.login_not_found', :default => "is not valid"))
return
end
if !attempted_record.send(verify_password_method, send("protected_#{password_field}"))
self.invalid_password = true
generalize_credentials_error_messages? ?
add_general_credentials_error :
errors.add(password_field, I18n.t('error_messages.password_invalid', :default => "is not valid"))
return
end
end
def invalid_password
@invalid_password
end
def invalid_password=(value)
@invalid_password = value
end
def find_by_login_method
self.class.find_by_login_method
end
def login_field
self.class.login_field
end
def add_general_credentials_error
error_message =
if self.class.generalize_credentials_error_messages.is_a? String
self.class.generalize_credentials_error_messages
else
"#{login_field.to_s.humanize}/Password combination is not valid"
end
errors.add(:base, I18n.t('error_messages.general_credentials_error', :default => error_message))
end
def generalize_credentials_error_messages?
self.class.generalize_credentials_error_messages
end
def password_field
self.class.password_field
end
def verify_password_method
self.class.verify_password_method
end
end
end
end
end

View file

@ -0,0 +1,18 @@
module Authlogic
module Session
# Maintains the perishable token, which is helpful for confirming records or authorizing records to reset their password. All that this
# module does is reset it after a session have been saved, just keep it changing. The more it changes, the tighter the security.
#
# See Authlogic::ActsAsAuthentic::PerishableToken for more information.
module PerishableToken
def self.included(klass)
klass.after_save :reset_perishable_token!
end
private
def reset_perishable_token!
record.reset_perishable_token if record.respond_to?(:reset_perishable_token) && !record.disable_perishable_token_maintenance?
end
end
end
end

View file

@ -0,0 +1,70 @@
module Authlogic
module Session
# Responsible for allowing you to persist your sessions.
module Persistence
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
end
end
module ClassMethods
# This is how you persist a session. This finds the record for the current session using
# a variety of methods. It basically tries to "log in" the user without the user having
# to explicitly log in. Check out the other Authlogic::Session modules for more information.
#
# The best way to use this method is something like:
#
# helper_method :current_user_session, :current_user
#
# def current_user_session
# return @current_user_session if defined?(@current_user_session)
# @current_user_session = UserSession.find
# end
#
# def current_user
# return @current_user if defined?(@current_user)
# @current_user = current_user_session && current_user_session.user
# end
#
# Also, this method accepts a single parameter as the id, to find session that you marked with an id:
#
# UserSession.find(:secure)
#
# See the id method for more information on ids.
def find(id = nil, priority_record = nil)
session = new({:priority_record => priority_record}, id)
session.priority_record = priority_record
if session.persisting?
session
else
nil
end
end
end
module InstanceMethods
# Let's you know if the session is being persisted or not, meaning the user does not have to explicitly log in
# in order to be logged in. If the session has no associated record, it will try to find a record and persis
# the session. This is the method that the class level method find uses to ultimately persist the session.
def persisting?
return true if !record.nil?
self.attempted_record = nil
before_persisting
persist
ensure_authentication_attempted
if errors.empty? && !attempted_record.nil?
self.record = attempted_record
after_persisting
save_record
self.new_session = false
true
else
false
end
end
end
end
end
end

View file

@ -0,0 +1,34 @@
module Authlogic
module Session
# The point of this module is to avoid the StaleObjectError raised when lock_version is implemented in ActiveRecord.
# We accomplish this by using a "priority record". Meaning this record is used if possible, it gets priority.
# This way we don't save a record behind the scenes thus making an object being used stale.
module PriorityRecord
def self.included(klass)
klass.class_eval do
attr_accessor :priority_record
end
end
# Setting priority record if it is passed. The only way it can be passed is through an array:
#
# session.credentials = [real_user_object, priority_user_object]
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
self.priority_record = values[1] if values[1].class < ::ActiveRecord::Base
end
private
def attempted_record=(value)
value = priority_record if value == priority_record
super
end
def save_record(alternate_record = nil)
r = alternate_record || record
super if r != priority_record
end
end
end
end

View file

@ -0,0 +1,101 @@
module Authlogic
module Session
# Authentication can be scoped, and it's easy, you just need to define how you want to scope everything. This should help you:
#
# 1. Want to scope by a parent object? Ex: An account has many users. Checkout Authlogic::AuthenticatesMany
# 2. Want to scope the validations in your model? Ex: 2 users can have the same login under different accounts. See Authlogic::ActsAsAuthentic::Scope
module Scopes # :nodoc:
def self.included(klass)
klass.class_eval do
extend ClassMethods
include InstanceMethods
attr_writer :scope
end
end
# = Scopes
module ClassMethods
# The current scope set, should be used in the block passed to with_scope.
def scope
Thread.current[:authlogic_scope]
end
# What with_scopes focuses on is scoping the query when finding the object and the name of the cookie / session. It works very similar to
# ActiveRecord::Base#with_scopes. It accepts a hash with any of the following options:
#
# * <tt>find_options:</tt> any options you can pass into ActiveRecord::Base.find. This is used when trying to find the record.
# * <tt>id:</tt> The id of the session, this gets merged with the real id. For information ids see the id method.
#
# Here is how you use it:
#
# UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
# UserSession.find
# end
#
# Eseentially what the above does is scope the searching of the object with the sql you provided. So instead of:
#
# User.find(:first, :conditions => "login = 'ben'")
#
# it would be:
#
# User.find(:first, :conditions => "login = 'ben' and account_id = 2")
#
# You will also notice the :id option. This works just like the id method. It scopes your cookies. So the name of your cookie will be:
#
# account_2_user_credentials
#
# instead of:
#
# user_credentials
#
# What is also nifty about scoping with an :id is that it merges your id's. So if you do:
#
# UserSession.with_scope(:find_options => {:conditions => "account_id = 2"}, :id => "account_2") do
# session = UserSession.new
# session.id = :secure
# end
#
# The name of your cookies will be:
#
# secure_account_2_user_credentials
def with_scope(options = {}, &block)
raise ArgumentError.new("You must provide a block") unless block_given?
self.scope = options
result = yield
self.scope = nil
result
end
private
def scope=(value)
Thread.current[:authlogic_scope] = value
end
end
module InstanceMethods
# Setting the scope if it exists upon instantiation.
def initialize(*args)
self.scope = self.class.scope
super
end
# The scope of the current object
def scope
@scope ||= {}
end
private
# Used for things like cookie_key, session_key, etc.
def build_key(last_part)
[scope[:id], super].compact.join("_")
end
def search_for_record(*args)
klass.send(:with_scope, :find => (scope[:find_options] || {})) do
klass.send(*args)
end
end
end
end
end
end

View file

@ -0,0 +1,62 @@
module Authlogic
module Session
# Handles all parts of authentication that deal with sessions. Such as persisting a session and saving / destroy a session.
module Session
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
persist :persist_by_session
after_save :update_session
after_destroy :update_session
after_persisting :update_session, :unless => :single_access?
end
end
# Configuration for the session feature.
module Config
# Works exactly like cookie_key, but for sessions. See cookie_key for more info.
#
# * <tt>Default:</tt> cookie_key
# * <tt>Accepts:</tt> Symbol or String
def session_key(value = nil)
rw_config(:session_key, value, cookie_key)
end
alias_method :session_key=, :session_key
end
# Instance methods for the session feature.
module InstanceMethods
private
# Tries to validate the session from information in the session
def persist_by_session
persistence_token, record_id = session_credentials
if !persistence_token.nil?
# Allow finding by persistence token, because when records are created the session is maintained in a before_save, when there is no id.
# This is done for performance reasons and to save on queries.
record = record_id.nil? ?
search_for_record("find_by_persistence_token", persistence_token) :
search_for_record("find_by_#{klass.primary_key}", record_id)
self.unauthorized_record = record if record && record.persistence_token == persistence_token
valid?
else
false
end
end
def session_credentials
[controller.session[session_key], controller.session["#{session_key}_#{klass.primary_key}"]].compact
end
def session_key
build_key(self.class.session_key)
end
def update_session
controller.session[session_key] = record && record.persistence_token
controller.session["#{session_key}_#{klass.primary_key}"] = record && record.send(record.class.primary_key)
end
end
end
end
end

View file

@ -0,0 +1,82 @@
module Authlogic
module Session
# Think about financial websites, if you are inactive for a certain period of time you will be asked to
# log back in on your next request. You can do this with Authlogic easily, there are 2 parts to this:
#
# 1. Define the timeout threshold:
#
# acts_as_authentic do |c|
# c.logged_in_timeout = 10.minutes # default is 10.minutes
# end
#
# 2. Enable logging out on timeouts
#
# class UserSession < Authlogic::Session::Base
# logout_on_timeout true # default if false
# end
#
# This will require a user to log back in if they are inactive for more than 10 minutes. In order for
# this feature to be used you must have a last_request_at datetime column in your table for whatever model
# you are authenticating with.
module Timeout
def self.included(klass)
klass.class_eval do
extend Config
include InstanceMethods
before_persisting :reset_stale_state
after_persisting :enforce_timeout
attr_accessor :stale_record
end
end
# Configuration for the timeout feature.
module Config
# With acts_as_authentic you get a :logged_in_timeout configuration option. If this is set, after this amount of time has passed the user
# will be marked as logged out. Obviously, since web based apps are on a per request basis, we have to define a time limit threshold that
# determines when we consider a user to be "logged out". Meaning, if they login and then leave the website, when do mark them as logged out?
# I recommend just using this as a fun feature on your website or reports, giving you a ballpark number of users logged in and active. This is
# not meant to be a dead accurate representation of a users logged in state, since there is really no real way to do this with web based apps.
# Think about a user that logs in and doesn't log out. There is no action that tells you that the user isn't technically still logged in and
# active.
#
# That being said, you can use that feature to require a new login if their session timesout. Similar to how financial sites work. Just set this option to
# true and if your record returns true for stale? then they will be required to log back in.
#
# Lastly, UserSession.find will still return a object is the session is stale, but you will not get a record. This allows you to determine if the
# user needs to log back in because their session went stale, or because they just aren't logged in. Just call current_user_session.stale? as your flag.
#
# * <tt>Default:</tt> false
# * <tt>Accepts:</tt> Boolean
def logout_on_timeout(value = nil)
rw_config(:logout_on_timeout, value, false)
end
alias_method :logout_on_timeout=, :logout_on_timeout
end
# Instance methods for the timeout feature.
module InstanceMethods
# Tells you if the record is stale or not. Meaning the record has timed out. This will only return true if you set logout_on_timeout to true in your configuration.
# Basically how a bank website works. If you aren't active over a certain period of time your session becomes stale and requires you to log back in.
def stale?
!stale_record.nil? || (logout_on_timeout? && record && record.logged_out?)
end
private
def reset_stale_state
self.stale_record = nil
end
def enforce_timeout
if stale?
self.stale_record = record
self.record = nil
end
end
def logout_on_timeout?
self.class.logout_on_timeout == true
end
end
end
end
end

View file

@ -0,0 +1,50 @@
module Authlogic
module Session
# Allows you to create session with an object. Ex:
#
# UserSession.create(my_user_object)
#
# Be careful with this, because Authlogic is assuming that you have already confirmed that the
# user is who he says he is.
#
# For example, this is the method used to persist the session internally. Authlogic finds the user with
# the persistence token. At this point we know the user is who he says he is, so Authlogic just creates a
# session with the record. This is particularly useful for 3rd party authentication methods, such as
# OpenID. Let that method verify the identity, once it's verified, pass the object and create a session.
module UnauthorizedRecord
def self.included(klass)
klass.class_eval do
attr_accessor :unauthorized_record
validate :validate_by_unauthorized_record, :if => :authenticating_with_unauthorized_record?
end
end
# Returning meaningful credentials
def credentials
if authenticating_with_unauthorized_record?
details = {}
details[:unauthorized_record] = "<protected>"
details
else
super
end
end
# Setting the unauthorized record if it exists in the credentials passed.
def credentials=(value)
super
values = value.is_a?(Array) ? value : [value]
self.unauthorized_record = values.first if values.first.class < ::ActiveRecord::Base
end
private
def authenticating_with_unauthorized_record?
!unauthorized_record.nil?
end
def validate_by_unauthorized_record
self.attempted_record = unauthorized_record
end
end
end
end

View file

@ -0,0 +1,82 @@
module Authlogic
module Session
# Responsible for session validation
module Validation
# The errors in Authlogic work JUST LIKE ActiveRecord. In fact, it uses the exact same ActiveRecord errors class. Use it the same way:
#
# class UserSession
# validate :check_if_awesome
#
# private
# def check_if_awesome
# errors.add(:login, "must contain awesome") if login && !login.include?("awesome")
# errors.add(:base, "You must be awesome to log in") unless attempted_record.awesome?
# end
# end
class Errors < (defined?(::ActiveModel) ? ::ActiveModel::Errors : ::ActiveRecord::Errors)
unless defined?(::ActiveModel)
def [](key)
value = super
value.is_a?(Array) ? value : [value].compact
end
end
end
# You should use this as a place holder for any records that you find during validation. The main reason for this is to
# allow other modules to use it if needed. Take the failed_login_count feature, it needs this in order to increase
# the failed login count.
def attempted_record
@attempted_record
end
# See attempted_record
def attempted_record=(value)
@attempted_record = value
end
# The errors in Authlogic 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(:base, "You must be awesome to log in") unless attempted_record.awesome?
# end
# end
def errors
@errors ||= Errors.new(self)
end
# Determines if the information you provided for authentication is valid or not. If there is
# a problem with the information provided errors will be added to the errors object and this
# method will return false.
def valid?
errors.clear
self.attempted_record = nil
before_validation
new_session? ? before_validation_on_create : before_validation_on_update
validate
ensure_authentication_attempted
if errors.size == 0
new_session? ? after_validation_on_create : after_validation_on_update
after_validation
end
save_record(attempted_record)
errors.size == 0
end
private
def ensure_authentication_attempted
errors.add(:base, I18n.t('error_messages.no_authentication_details', :default => "You did not provide any details for authentication.")) if errors.empty? && attempted_record.nil?
end
end
end
end

View file

@ -0,0 +1,120 @@
require File.dirname(__FILE__) + "/test_case/rails_request_adapter"
require File.dirname(__FILE__) + "/test_case/mock_cookie_jar"
require File.dirname(__FILE__) + "/test_case/mock_controller"
require File.dirname(__FILE__) + "/test_case/mock_logger"
require File.dirname(__FILE__) + "/test_case/mock_request"
module Authlogic
# This module is a collection of methods and classes that help you easily test Authlogic. In fact,
# I use these same tools to test the internals of Authlogic.
#
# === The quick and dirty
#
# require "authlogic/test_case" # include at the top of test_helper.rb
# setup :activate_authlogic # run before tests are executed
# UserSession.create(users(:whomever)) # logs a user in
#
# For a more detailed explanation, see below.
#
# === Setting up
#
# Authlogic comes with some simple testing tools. To get these, you need to first require Authlogic's TestCase. If
# you are doing this in a rails app, you would require this file at the top of your test_helper.rb file:
#
# require "authlogic/test_case"
#
# If you are using Test::Unit::TestCase, the standard testing library that comes with ruby, then you can skip this next part.
# If you are not, you need to include the Authlogic::TestCase into your testing suite as follows:
#
# include Authlogic::TestCase
#
# Now that everything is ready to go, let's move onto actually testing. Here is the basic idea behind testing:
#
# Authlogic requires a "connection" to your controller to activate it. In the same manner that ActiveRecord requires a connection to
# your database. It can't do anything until it gets connnected. That being said, Authlogic will raise an
# Authlogic::Session::Activation::NotActivatedError any time you try to instantiate an object without a "connection".
# So before you do anything with Authlogic, you need to activate / connect Authlogic. Let's walk through how to do this in tests:
#
# === Fixtures / Factories
#
# Creating users via fixtures / factories is easy. Here's an example of a fixture:
#
# ben:
# email: whatever@whatever.com
# password_salt: <%= salt = Authlogic::Random.hex_token %>
# crypted_password: <%= Authlogic::CryptoProviders::Sha512.encrypt("benrocks" + salt) %>
# persistence_token: <%= Authlogic::Random.hex_token %>
# single_access_token: <%= Authlogic::Random.friendly_token %>
# perishable_token: <%= Authlogic::Random.friendly_token %>
#
# Notice the crypted_password value. Just supplement that with whatever crypto provider you are using, if you are not using the default.
#
# === Functional tests
#
# Activating Authlogic isn't a problem here, because making a request will activate Authlogic for you. The problem is
# logging users in so they can access restricted areas. Solving this is simple, just do this:
#
# setup :activate_authlogic
#
# For those of you unfamiliar with TestUnit, the setup method bascially just executes a method before any test is ran.
# It is essentially "setting up" your tests.
#
# Once you have done this, just log users in like usual:
#
# UserSession.create(users(:whomever))
# # access my restricted area here
#
# Do this before you make your request and it will act as if that user is logged in.
#
# === Integration tests
#
# Again, just like functional tests, you don't have to do anything. As soon as you make a request, Authlogic will be
# conntected. If you want to activate Authlogic before making a request follow the same steps described in the
# "functional tests" section above. It works in the same manner.
#
# === Unit tests
#
# The only time you need to do any trickiness here is if you want to test Authlogic models. Maybe you added some custom
# code or methods in your Authlogic models. Maybe you are writing a plugin or a library that extends Authlogic.
#
# That being said, in this environment there is no controller. So you need to use a "mock" controller. Something
# that looks like a controller, acts like a controller, but isn't a "real" controller. You are essentially connecting
# Authlogic to your "mock" controller, then you can test off of the mock controller to make sure everything is functioning
# properly.
#
# I use a mock controller to test Authlogic myself. It's part of the Authlogic library that you can easily use. It's as simple
# as functional and integration tests. Just do the following:
#
# setup :activate_authlogic
#
# You also get a controller method that you can test off of. For example:
#
# ben = users(:ben)
# assert_nil controller.session["user_credentials"]
# assert UserSession.create(ben)
# assert_equal controller.session["user_credentials"], ben.persistence_token
#
# See how I am checking that Authlogic is interacting with the controller properly? That's the idea here.
module TestCase
# Activates authlogic so that you can use it in your tests. You should call this method in your test's setup. Ex:
#
# setup :activate_authlogic
def activate_authlogic
if @request && ! @request.respond_to?(:params)
class <<@request
alias_method :params, :parameters
end
end
Authlogic::Session::Base.controller = (@request && Authlogic::TestCase::RailsRequestAdapter.new(@request)) || controller
end
# The Authlogic::TestCase::MockController object passed to Authlogic to activate it. You can access this in your test.
# See the module description for an example.
def controller
@controller ||= Authlogic::TestCase::MockController.new
end
end
::Test::Unit::TestCase.send(:include, TestCase) if defined?(::Test::Unit::TestCase)
end

View file

@ -0,0 +1,45 @@
module Authlogic
module TestCase
# Basically acts like a controller but doesn't do anything. Authlogic can interact with this, do it's thing and then you
# can look at the controller object to see if anything changed.
class MockController < ControllerAdapters::AbstractAdapter
attr_accessor :http_user, :http_password
attr_writer :request_content_type
def initialize
end
def authenticate_with_http_basic(&block)
yield http_user, http_password
end
def cookies
@cookies ||= MockCookieJar.new
end
def cookie_domain
nil
end
def logger
@logger ||= MockLogger.new
end
def params
@params ||= {}
end
def request
@request ||= MockRequest.new(controller)
end
def request_content_type
@request_content_type ||= "text/html"
end
def session
@session ||= {}
end
end
end
end

View file

@ -0,0 +1,14 @@
module Authlogic
module TestCase
class MockCookieJar < Hash # :nodoc:
def [](key)
hash = super
hash && hash[:value]
end
def delete(key, options = {})
super(key)
end
end
end
end

View file

@ -0,0 +1,10 @@
module Authlogic
module TestCase
# Simple class to replace real loggers, so that we can raise any errors being logged.
class MockLogger
def error(message)
raise message
end
end
end
end

View file

@ -0,0 +1,19 @@
module Authlogic
module TestCase
class MockRequest # :nodoc:
attr_accessor :controller
def initialize(controller)
self.controller = controller
end
def remote_ip
(controller && controller.respond_to?(:env) && controller.env.is_a?(Hash) && controller.env['REMOTE_ADDR']) || "1.1.1.1"
end
private
def method_missing(*args, &block)
end
end
end
end

View file

@ -0,0 +1,30 @@
module Authlogic
module TestCase
# Adapts authlogic to work with the @request object when testing. This way Authlogic can set cookies and what not before
# a request is made, ultimately letting you log in users in functional tests.
class RailsRequestAdapter < ControllerAdapters::AbstractAdapter
def authenticate_with_http_basic(&block)
end
def cookies
new_cookies = MockCookieJar.new
super.each do |key, value|
new_cookies[key] = value[:value]
end
new_cookies
end
def cookie_domain
nil
end
def request
@request ||= MockRequest.new(controller)
end
def request_content_type
request.format.to_s
end
end
end
end

Some files were not shown because too many files have changed in this diff Show more