Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 1b98335cc7b6a9514b13adbdf61035750e8b873b 0 parents
@binarylogic binarylogic authored
Showing with 9,678 additions and 0 deletions.
  1. +7 −0 .gitignore
  2. +20 −0 MIT-LICENSE
  3. +85 −0 Manifest
  4. +164 −0 README.rdoc
  5. +15 −0 Rakefile
  6. +2 −0  init.rb
  7. +18 −0 lib/authgasm.rb
  8. +200 −0 lib/authgasm/acts_as_authentic.rb
  9. +16 −0 lib/authgasm/controller.rb
  10. +30 −0 lib/authgasm/session/active_record_trickery.rb
  11. +365 −0 lib/authgasm/session/base.rb
  12. +47 −0 lib/authgasm/session/callbacks.rb
  13. +193 −0 lib/authgasm/session/config.rb
  14. +12 −0 lib/authgasm/session/errors.rb
  15. +13 −0 lib/authgasm/sha256_crypto_provider.rb
  16. +56 −0 lib/authgasm/version.rb
  17. +10 −0 test_app/.gitignore
  18. +256 −0 test_app/README
  19. +10 −0 test_app/Rakefile
  20. +46 −0 test_app/app/controllers/application.rb
  21. +25 −0 test_app/app/controllers/user_sessions_controller.rb
  22. +37 −0 test_app/app/controllers/users_controller.rb
  23. +3 −0  test_app/app/helpers/application_helper.rb
  24. +2 −0  test_app/app/helpers/user_sessions_helper.rb
  25. +2 −0  test_app/app/helpers/users_helper.rb
  26. +3 −0  test_app/app/models/user.rb
  27. +3 −0  test_app/app/models/user_session.rb
  28. +12 −0 test_app/app/views/asses/edit.html.erb
  29. +18 −0 test_app/app/views/asses/index.html.erb
  30. +11 −0 test_app/app/views/asses/new.html.erb
  31. +3 −0  test_app/app/views/asses/show.html.erb
  32. +25 −0 test_app/app/views/layouts/application.html.erb
  33. +13 −0 test_app/app/views/user_sessions/new.html.erb
  34. +15 −0 test_app/app/views/users/_form.erb
  35. +8 −0 test_app/app/views/users/edit.html.erb
  36. +8 −0 test_app/app/views/users/new.html.erb
  37. +19 −0 test_app/app/views/users/show.html.erb
  38. +109 −0 test_app/config/boot.rb
  39. +69 −0 test_app/config/environment.rb
  40. +17 −0 test_app/config/environments/development.rb
  41. +22 −0 test_app/config/environments/production.rb
  42. +22 −0 test_app/config/environments/test.rb
  43. +10 −0 test_app/config/initializers/inflections.rb
  44. +5 −0 test_app/config/initializers/mime_types.rb
  45. +17 −0 test_app/config/initializers/new_rails_defaults.rb
  46. +7 −0 test_app/config/routes.rb
  47. BIN  test_app/db/development.sqlite3
  48. +17 −0 test_app/db/migrate/20081023040052_create_users.rb
  49. BIN  test_app/db/test.sqlite3
  50. +2 −0  test_app/doc/README_FOR_APP
  51. +30 −0 test_app/public/404.html
  52. +30 −0 test_app/public/422.html
  53. +30 −0 test_app/public/500.html
  54. +10 −0 test_app/public/dispatch.cgi
  55. +24 −0 test_app/public/dispatch.fcgi
  56. +10 −0 test_app/public/dispatch.rb
  57. 0  test_app/public/favicon.ico
  58. BIN  test_app/public/images/rails.png
  59. +2 −0  test_app/public/javascripts/application.js
  60. +963 −0 test_app/public/javascripts/controls.js
  61. +972 −0 test_app/public/javascripts/dragdrop.js
  62. +1,120 −0 test_app/public/javascripts/effects.js
  63. +4,225 −0 test_app/public/javascripts/prototype.js
  64. +5 −0 test_app/public/robots.txt
  65. +62 −0 test_app/public/stylesheets/scaffold.css
  66. +4 −0 test_app/script/about
  67. +3 −0  test_app/script/console
  68. +3 −0  test_app/script/dbconsole
  69. +3 −0  test_app/script/destroy
  70. +3 −0  test_app/script/generate
  71. +3 −0  test_app/script/performance/benchmarker
  72. +3 −0  test_app/script/performance/profiler
  73. +3 −0  test_app/script/performance/request
  74. +3 −0  test_app/script/plugin
  75. +3 −0  test_app/script/process/inspector
  76. +3 −0  test_app/script/process/reaper
  77. +3 −0  test_app/script/process/spawner
  78. +3 −0  test_app/script/runner
  79. +3 −0  test_app/script/server
  80. +6 −0 test_app/test/fixtures/users.yml
  81. +15 −0 test_app/test/functional/user_sessions_controller_test.rb
  82. +8 −0 test_app/test/functional/users_controller_test.rb
  83. +38 −0 test_app/test/test_helper.rb
  84. +8 −0 test_app/test/unit/ass_test.rb
  85. +8 −0 test_app/test/unit/user_test.rb
7 .gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+*.log
+pkg/*
+coverage/*
+doc/*
+benchmarks/*
+
20 MIT-LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2007 Ben Johnson of Binary Logic (binarylogic.com)
+
+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.
85 Manifest
@@ -0,0 +1,85 @@
+init.rb
+lib/authgasm/acts_as_authentic.rb
+lib/authgasm/controller.rb
+lib/authgasm/session/active_record_trickery.rb
+lib/authgasm/session/base.rb
+lib/authgasm/session/callbacks.rb
+lib/authgasm/session/config.rb
+lib/authgasm/session/errors.rb
+lib/authgasm/sha256_crypto_provider.rb
+lib/authgasm/version.rb
+lib/authgasm.rb
+Manifest
+MIT-LICENSE
+Rakefile
+README.rdoc
+test_app/app/controllers/application.rb
+test_app/app/controllers/user_sessions_controller.rb
+test_app/app/controllers/users_controller.rb
+test_app/app/helpers/application_helper.rb
+test_app/app/helpers/user_sessions_helper.rb
+test_app/app/helpers/users_helper.rb
+test_app/app/models/user.rb
+test_app/app/models/user_session.rb
+test_app/app/views/asses/edit.html.erb
+test_app/app/views/asses/index.html.erb
+test_app/app/views/asses/new.html.erb
+test_app/app/views/asses/show.html.erb
+test_app/app/views/layouts/application.html.erb
+test_app/app/views/user_sessions/new.html.erb
+test_app/app/views/users/_form.erb
+test_app/app/views/users/edit.html.erb
+test_app/app/views/users/new.html.erb
+test_app/app/views/users/show.html.erb
+test_app/config/boot.rb
+test_app/config/database.yml
+test_app/config/environment.rb
+test_app/config/environments/development.rb
+test_app/config/environments/production.rb
+test_app/config/environments/test.rb
+test_app/config/initializers/inflections.rb
+test_app/config/initializers/mime_types.rb
+test_app/config/initializers/new_rails_defaults.rb
+test_app/config/routes.rb
+test_app/db/development.sqlite3
+test_app/db/migrate/20081023040052_create_users.rb
+test_app/db/schema.rb
+test_app/db/test.sqlite3
+test_app/doc/README_FOR_APP
+test_app/public/404.html
+test_app/public/422.html
+test_app/public/500.html
+test_app/public/dispatch.cgi
+test_app/public/dispatch.fcgi
+test_app/public/dispatch.rb
+test_app/public/favicon.ico
+test_app/public/images/rails.png
+test_app/public/javascripts/application.js
+test_app/public/javascripts/controls.js
+test_app/public/javascripts/dragdrop.js
+test_app/public/javascripts/effects.js
+test_app/public/javascripts/prototype.js
+test_app/public/robots.txt
+test_app/public/stylesheets/scaffold.css
+test_app/Rakefile
+test_app/README
+test_app/script/about
+test_app/script/console
+test_app/script/dbconsole
+test_app/script/destroy
+test_app/script/generate
+test_app/script/performance/benchmarker
+test_app/script/performance/profiler
+test_app/script/performance/request
+test_app/script/plugin
+test_app/script/process/inspector
+test_app/script/process/reaper
+test_app/script/process/spawner
+test_app/script/runner
+test_app/script/server
+test_app/test/fixtures/users.yml
+test_app/test/functional/user_sessions_controller_test.rb
+test_app/test/functional/users_controller_test.rb
+test_app/test/test_helper.rb
+test_app/test/unit/ass_test.rb
+test_app/test/unit/user_test.rb
164 README.rdoc
@@ -0,0 +1,164 @@
+= Authgasm
+
+Authgasm is "RESTful rails authentication done right"
+
+The last thing we need is another authentication solution for rails, right? That's what I thought. It was disappointing to find that all of the solutions were overly complicated, bloated, made too many assumptions about my app, written poorly, or were just plain confusing. I wanted something simple. Something that feels like it is a part of rails. Something that I could understand and not feel like authentication is this daunting / annoying task that litters my application with redundant code. So I decided to scratch my own itch by creating Authgasm.
+
+Wouldn't it be nice if we could do something like:
+
+ class UserSessionsController < ApplicationController
+ def new
+ @user_session = UserSession.new
+ end
+
+ def create
+ @user_session = UserSession.new(params[:user_session])
+ if @user_session.create
+ redirect_to my_account_url
+ else
+ render :action => :new
+ end
+ end
+
+ def destroy
+ @user_session.destroy
+ end
+ end
+
+Look familiar? If you didn't know any better, you would think UserSession was an ActiveRecord model. I think that's pretty cool. Why is that cool? Because it fits nicely into the RESTful development pattern and its a style we all know and love. Wouldn't this be cool too...
+
+ <%= error_messages_for "user_session" %>
+ <% form_for @user_session do |f| %>
+ <%= f.label :login %><br />
+ <%= f.text_field :login %><br />
+ <br />
+ <%= f.label :password %><br />
+ <%= f.password_field :password %><br />
+ <br />
+ <%= f.submit "Login" %>
+ <% end %>
+
+Oh, and how about this...
+
+ class ApplicationController
+ before_filter :load_user
+
+ protected
+ def load_user
+ @user_session = UserSession.find
+ @current_user = @user_session && @user_session.record
+ end
+ end
+
+Authgasm makes this a reality. Hopefully I got your interest. This is just the tip of the ice berg. Keep reading to find out everything Authgasm can do.
+
+== Helpful links
+
+* <b>Documentation:</b> http://authgasm.rubyforge.org
+* <b>Authgasm tutorial:</b> coming soon...
+* <b>Live example of the tutorial above (with source):</b> coming soon....
+* <b>Bugs / feature suggestions:</b> http://binarylogic.lighthouseapp.com/projects/18752-authgasm
+
+== Install and use
+
+Installing Authgasm and setting it up is very simple. Just like rails, Authgasm favors convention over configuration. As a result, it assumes a few things about your app. This guide will walk you through setting up Authgasm in your app and what Authgasm assumes.
+
+=== Install the gem / plugin
+
+ $ sudo gem install authgasm
+ $ cd vendor/plugins
+ $ sudo gem unpack authgasm
+
+Or as a plugin
+
+ script/plugin install git://github.com/binarylogic/authgasm.git
+
+=== Configuration
+
+Before we start, it is important you understand the basics behind Authgasm. Authgasm is split into 2 parts.
+
+1. Your model that you will be authenticating with, such as User
+2. Your session that represents a login, such as UserSession
+
+Each have their own configuration, so it can be as flexible as you need it to be. What's convenient is that the configuration for your model defaults to the configuration you set in your session. So if you set the configuration in your session, you won't have to repeat yourself in your model.
+
+For information on configuration please see Searchgasm::ActsAsAuthentic and Authgasm::Session::Config::ClassMethods
+
+=== Set up your model
+
+Make sure you have a model that you will be authenticating with. For this example let's say you have a User model:
+
+ class User < ActiveRecord::Base
+ acts_as_authentic # for options see documentation: Authgasm::ActsAsAuthentic
+ end
+
+The user model needs to have the following columns. The names of these columns can be changed with configuration.
+
+ t.string :login, :null => false
+ t.string :crypted_password, :null => false
+ t.string :password_salt, :null => false # not needed if you are encrypting your pw instead of using a hash algorithm
+ t.string :remember_token, :null => false
+ t.integer :loging_count # This is optional, it is a "magic" column, just like "created_at". See below for a list of all magic columns.
+
+Create your user_session.rb file:
+
+ # app/models/user_session.rb
+ class UserSession < Authgasm::Session::Base
+ end
+
+Done! Now go use it just like you would with any other ActiveRecord model (see above).
+
+== Magic Columns
+
+Just like ActiveRecord has "magic" columns, such as: created_at and updated_at. Authgasm has its own "magic" columns too:
+
+ Column name Description
+ login_count Increased every time and explicit login is made. This will *NOT* increase if logging in by a session, cookie, or basic http auth
+ last_click_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.
+
+== Magic States
+
+Authgasm 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
+ approved? Has the record been approved?
+ confirmed? Has the record been conirmed?
+ inactive? Is the record marked as inactive?
+
+What's neat about these is that these are checked upon any type of login. When logging in explicitly, by cookie, session, or basic http auth. If any of these return false validation will fail and a session will not be created.
+
+== Hooks / Callbacks
+
+Just like ActiveRecord you can create your own hooks / callbacks so that you can do whatever you want when certain actions are performed. Here they are:
+
+ before_create
+ after_create
+ before_destroy
+ after_destroy
+ before_update
+ after_update
+ before_validation
+ after_validation
+
+== Automatic Session Updating
+
+This is one of my favorite features that I think is pretty cool. What if a user changes their password? You have to re-log them in with the new password, recreate the session, etc, pain in the ass. Or what if a user creates a new user account? You have to do the same thing. It makes your UsersController kind of dirty and it's kind of annoying. What's cool about this is that we pulled the UserSession down into the models, where we can play around with it. Why not have the User model take care of this for us in an after_save? Whoa! Now you don't have to worry about it at all. In fact, the acts_as_authentic method has an option to do this automatically for you. Zing! Man, Authgasm might be a little too awesome. So...
+
+ @current_user.password = "my new password"
+ @current_user.confirm_password = "my new password"
+ @current_user.save # automatically updates the sessions for you!
+
+When things come together like this I think its a sign that you are doing something right. Put that in your pipe and smoke it!
+
+== How it works
+
+Interested in how this all works. Basically a before_filter is set in your controller which lets Authgasm know about the current controller object. This allows Authgasm to set sessions, cookies, login via basic http auth, etc. Don't worry, this is thread safe.
+
+From there is it pretty simple. When you try to create a new session the record is authenticated and then all of the session / cookie magic is done for you.
+
+
+Copyright (c) 2008 Ben Johnson of [Binary Logic](http://www.binarylogic.com), released under the MIT license
15 Rakefile
@@ -0,0 +1,15 @@
+require 'rubygems'
+require 'echoe'
+
+require File.dirname(__FILE__) << "/lib/authgasm/version"
+
+Echoe.new 'authgasm' do |p|
+ p.version = Authgasm::Version::STRING
+ p.author = "Ben Johnson of Binary Logic"
+ p.email = 'bjohnson@binarylogic.com'
+ p.project = 'authgasm'
+ p.summary = "Rails authentication done right"
+ p.url = "http://github.com/binarylogic/authgasm"
+ p.dependencies = %w(activesupport activerecord)
+ p.include_rakefile = true
+end
2  init.rb
@@ -0,0 +1,2 @@
+require "digest/sha2"
+require "authgasm"
18 lib/authgasm.rb
@@ -0,0 +1,18 @@
+require File.dirname(__FILE__) + "/authgasm/version"
+require File.dirname(__FILE__) + "/authgasm/controller"
+require File.dirname(__FILE__) + "/authgasm/sha256_crypto_provider"
+require File.dirname(__FILE__) + "/authgasm/acts_as_authentic"
+require File.dirname(__FILE__) + "/authgasm/session/active_record_trickery"
+require File.dirname(__FILE__) + "/authgasm/session/callbacks"
+require File.dirname(__FILE__) + "/authgasm/session/config"
+require File.dirname(__FILE__) + "/authgasm/session/errors"
+require File.dirname(__FILE__) + "/authgasm/session/base"
+
+module Authgasm
+ module Session
+ class Base
+ include ActiveRecordTrickery
+ include Callbacks
+ end
+ end
+end
200 lib/authgasm/acts_as_authentic.rb
@@ -0,0 +1,200 @@
+module Authgasm
+ module ActsAsAuthenticated # :nodoc:
+ def self.included(base)
+ base.extend(ClassMethods)
+ end
+
+ # = Acts As Authentic
+ # Provides and "acts_as" method to include in your models to help with authentication. See method below.
+ module ClassMethods
+ # Call this method in your model to add in basic authentication madness:
+ #
+ # 1. Adds various validations for the login field
+ # 2. Adds various validations for the password field
+ # 3. Handles password encryption
+ # 4. Adds usefule methods to dealing with authentication
+ #
+ # === Methods
+ # For example purposes lets assume you have a User model.
+ #
+ # Class method name Description
+ # User.unique_token returns unique token generated by your :crypto_provider
+ # User.crypto_provider The class that you set in your :crypto_provider option
+ #
+ # Named Scopes
+ # User.logged_in Find all users who are logged in, based on your :logged_in_timeout option
+ # User.logged_out Same as above, but logged out
+ #
+ # Isntace method name
+ # user.password= Method name based on the :password_field option. This is used to set the password. Pass the *raw* password to this
+ # user.confirm_password= Confirms the password, needed to change the password
+ # user.valid_password?(pass) Based on the valid of :password_field. Determines if the password passed is valid. The password could be encrypted or raw.
+ # user.randomize_password! Basically resets the password to a random password using only letters and numbers
+ # user.logged_in? Based on the :logged_in_timeout option. Tells you if the user is logged in or not
+ #
+ # === Options
+ # * <tt>session_class:</tt> default: "#{name}Session", the related session class. Used so that you don't have to repeat yourself here. A lot of the configuration will be based off of the configuration values of this class.
+ # * <tt>crypto_provider:</tt> default: Authgasm::Sha256CryptoProvider, class that provides Sha256 encryption. What ultimately encrypts your password.
+ # * <tt>crypto_provider_type:</tt> default: options[:crypto_provider].respond_to?(:decrypt) ? :encryption : :hash. You can explicitly set this if you wish. Since encryptions and hashes are handled different this is the flag Authgasm uses.
+ # * <tt>login_field:</tt> default: options[:session_class].login_field, the name of the field used for logging in
+ # * <tt>login_field_type:</tt> default: options[:login_field] == :email ? :email : :login, tells authgasm how to validation the field, what regex to use, etc.
+ # * <tt>password_field:</tt> default: options[:session_class].password_field, the name of the field to set the password, *NOT* the field the encrypted password is stored
+ # * <tt>crypted_password_field:</tt> default: depends on which columns are present, checks: crypted_password, encrypted_password, password_hash, pw_hash, if none are present defaults to crypted_password. This is the name of column that your encrypted password is stored.
+ # * <tt>password_salt_field:</tt> default: depends on which columns are present, checks: password_salt, pw_salt, salt, if none are present defaults to password_salt. This is the name of the field your salt is stored, only relevant for a hash crypto provider.
+ # * <tt>remember_token_field:</tt> default: options[:session_class].remember_token_field, the name of the field your remember token is stored. What the cookie stores so the session can be "remembered"
+ # * <tt>logged_in_timeout:</tt> default: 10.minutes, this allows you to specify a time the determines if a user is logged in or out. Useful if you want to count how many users are currently logged in.
+ # * <tt>session_scopes:</tt> default: [nil], the sessions that we want to automatically reset when a user is created or updated so you don't have to worry about this. Set to [] to disable. Should be an array of scopes. See Authgasm::Session::Base#initialize for information on scopes.
+ def acts_as_authentic(options = {})
+ # Setup default options
+ options[:session_class] ||= "#{name}Session".constantize
+ options[:crypto_provider] ||= Sha256CryptoProvider
+ options[:crypto_provider_type] ||= options[:crypto_provider].respond_to?(:decrypt) ? :encryption : :hash
+ options[:login_field] ||= options[:session_class].login_field
+ options[:login_field_type] ||= options[:login_field] == :email ? :email : :login
+ options[:password_field] ||= options[:session_class].password_field
+ options[:crypted_password_field] ||=
+ (columns.include?("crypted_password") && :crypted_password) ||
+ (columns.include?("encrypted_password") && :encrypted_password) ||
+ (columns.include?("password_hash") && :password_hash) ||
+ (columns.include?("pw_hash") && :pw_hash) ||
+ :crypted_password
+ options[:password_salt_field] ||=
+ (columns.include?("password_salt") && :password_salt) ||
+ (columns.include?("pw_salt") && :pw_salt) ||
+ (columns.include?("salt") && :salt) ||
+ :password_salt
+ options[:remember_token_field] ||= options[:session_class].remember_token_field
+ options[:logged_in_timeout] ||= 10.minutes
+ options[:session_scopes] ||= [nil]
+
+ # Validations
+ case options[:login_field_type]
+ when :email
+ validates_length_of options[:login_field], :within => 6..100
+ email_name_regex = '[\w\.%\+\-]+'
+ domain_head_regex = '(?:[A-Z0-9\-]+\.)+'
+ domain_tld_regex = '(?:[A-Z]{2}|com|org|net|edu|gov|mil|biz|info|mobi|name|aero|jobs|museum)'
+ email_regex = /\A#{email_name_regex}@#{domain_head_regex}#{domain_tld_regex}\z/i
+ validates_format_of options[:login_field], :with => email_regex, :message => "should look like an email address."
+ else
+ validates_length_of options[:login_field], :within => 2..100
+ validates_format_of options[:login_field], :with => /\A\w[\w\.\-_@]+\z/, :message => "use only letters, numbers, and .-_@ please."
+ end
+
+ validates_uniqueness_of options[:login_field]
+ validate :validate_password
+ validates_numericality_of :login_count, :only_integer => :true, :greater_than_or_equal_to => 0, :allow_nil => true if column_names.include?("login_count")
+
+ if column_names.include?("last_click_at")
+ named_scope :logged_in, lambda { {:conditions => ["last_click_at > ?", options[:logged_in_timeout].ago]} }
+ named_scope :logged_out, lambda { {:conditions => ["last_click_at <= ?", options[:logged_in_timeout].ago]} }
+ end
+
+ after_create :create_sessions!
+ after_create :update_sessions!
+
+ # Attributes
+ attr_writer "confirm_#{options[:password_field]}"
+ attr_accessor "tried_to_set_#{options[:password_field]}", :saving_from_session
+
+ # Class methods
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def self.unique_token
+ crypto_provider.encrypt(Time.now.to_s + (1..10).collect{ rand.to_s }.join)
+ end
+
+ def self.crypto_provider
+ #{options[:crypto_provider]}
+ end
+ end_eval
+
+ # Instance methods
+ if column_names.include?("last_click_at")
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def logged_in?
+ !last_click_at.nil? && last_click_at > #{options[:logged_in_timeout].to_i}.seconds.ago
+ end
+ end_eval
+ end
+
+ case options[:crypto_provider_type]
+ when :hash
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{options[:password_field]}=(pass)
+ return if pass.blank?
+ self.tried_to_set_#{options[:password_field]} = true
+ @#{options[:password_field]} = pass
+ salt = [Array.new(6) {rand(256).chr}.join].pack("m").chomp
+ self.#{options[:remember_token_field]} = self.class.unique_token
+ self.#{options[:password_salt_field]}, self.#{options[:crypted_password_field]} = salt, crypto_provider.encrypt(@#{options[:password_field]} + salt)
+ end
+
+ def valid_#{options[:password_field]}?(attempted_password)
+ attempted_password == #{options[:crypted_password_field]} || #{options[:crypted_password_field]} == crypto_provider.encrypt(attempted_password + #{options[:password_salt_field]})
+ end
+ end_eval
+ when :encryption
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{options[:password_field]}=(pass)
+ return if pass.blank?
+ self.tried_to_set_#{options[:password_field]} = true
+ @#{options[:password_field]} = pass
+ self.#{options[:remember_token_field]} = self.class.unique_token
+ self.#{options[:crypted_password_field]} = crypto_provider.encrypt(@#{options[:password_field]})
+ end
+
+ def valid_#{options[:password_field]}?(attemtped_password)
+ attempted_password == #{options[:crypted_password_field]} || #{options[:crypted_password_field]} = crypto_provider.decrypt(attempted_password)
+ end
+ end_eval
+ end
+
+ class_eval <<-"end_eval", __FILE__, __LINE__
+ def #{options[:password_field]}; end
+ def confirm_#{options[:password_field]}; end
+
+ def crypto_provider
+ self.class.crypto_provider
+ end
+
+ def randomize_#{options[:password_field]}!
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ newpass = ""
+ 1.upto(10) { |i| newpass << chars[rand(chars.size-1)] }
+ self.#{options[:password_field]} = newpass
+ self.confirm_#{options[:password_field]} = newpass
+ end
+
+ protected
+ def create_sessions!
+ #{options[:session_scopes].inspect}.each { |scope| #{options[:session_class]}.create(self) }
+ end
+
+ def update_sessions!
+ #{options[:session_scopes].inspect}.each { |scope| #{options[:session_class]}.update(self) }
+ end
+
+ def saving_from_session?
+ saving_from_session == true
+ end
+
+ def tried_to_set_password?
+ tried_to_set_password == true
+ end
+
+ def validate_password
+ if new_record? || tried_to_set_#{options[:password_field]}?
+ if @#{options[:password_field]}.blank?
+ errors.add(:#{options[:password_field]}, "can not be blank")
+ else
+ errors.add(:confirm_#{options[:password_field]}, "did not match") if @confirm_#{options[:password_field]} != @#{options[:password_field]}
+ end
+ end
+ end
+ end_eval
+ end
+ end
+ end
+end
+
+ActiveRecord::Base.send(:include, Authgasm::ActsAsAuthenticated)
16 lib/authgasm/controller.rb
@@ -0,0 +1,16 @@
+module Authgasm
+ # = Controller
+ # Adds a before_filter to set the controller object so that Authgasm can do its session and cookie magic
+ module Controller
+ def self.included(klass) # :nodoc:
+ klass.prepend_before_filter :set_controller
+ end
+
+ private
+ def set_controller
+ Authgasm::Session::Base.controller = self
+ end
+ end
+end
+
+ActionController::Base.send(:include, Authgasm::Controller)
30 lib/authgasm/session/active_record_trickery.rb
@@ -0,0 +1,30 @@
+module Authgasm
+ module Session
+ # = ActiveRecord Trickery
+ #
+ # Authgasm 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, etc.
+ # These helpers exptect various methods to be present. This adds in those methods into Authgasm.
+ module ActiveRecordTrickery
+ def self.included(klass) # :nodoc:
+ klass.extend ClassMethods
+ klass.send(:include, InstanceMethods)
+ end
+
+ module ClassMethods # :nodoc:
+ def human_attribute_name(attribute_key_name, options = {})
+ attribute_key_name.humanize
+ end
+ end
+
+ module InstanceMethods # :nodoc:
+ def id
+ nil
+ end
+
+ def new_record?
+ true
+ end
+ end
+ end
+ end
+end
365 lib/authgasm/session/base.rb
@@ -0,0 +1,365 @@
+module Authgasm
+ module Session # :nodoc:
+ # = Base
+ #
+ # This is the muscle behind Authgasm. For detailed information on how to use this please refer to the README. For detailed method explanations see below.
+ class Base
+ include Config
+
+ class << self
+ # Returns true if a controller have been set and can be used properly.
+ def activated?
+ !controller.blank?
+ end
+
+ def controller=(value) # :nodoc:
+ controllers[Thread.current] = value
+ end
+
+ def controller # :nodoc:
+ controllers[Thread.current]
+ end
+
+ # A convenince method. The same as:
+ #
+ # session = UserSession.new
+ # session.create
+ def create(*args)
+ session = new(*args)
+ session.create
+ end
+
+ # Same as create but calls create!, which raises an exception when authentication fails
+ def create!(*args)
+ session = new(*args)
+ session.create!
+ end
+
+ # Finds your session by session, then cookie, and finally basic http auth. Perfect for that global before_filter to find your logged in user:
+ #
+ # before_filter :load_user
+ #
+ # def load_user
+ # @user_session = UserSession.find
+ # @current_user = @user_session && @user_session.record
+ # end
+ #
+ # Accepts a single parameter as the scope. See initialize for more information on scopes.
+ def find(scope = nil)
+ args = [scope].compact
+ session = new(*args)
+ return session if session.valid_session? || session.valid_cookie?(true) || session.valid_http_auth?(true)
+ nil
+ end
+
+ def klass # :nodoc:
+ @klass ||=
+ if klass_name
+ klass_name.constantize
+ else
+ nil
+ end
+ end
+
+ def klass_name # :nodoc:
+ @klass_name ||=
+ if guessed_name = name.scan(/(.*)Session/)[0]
+ @klass_name = guessed_name[0]
+ end
+ end
+
+ # Convenience method. The same as:
+ #
+ # session = UserSession.new
+ # session.update
+ def update(*args)
+ session = new(*args)
+ session.update
+ end
+
+ # The same as update but calls update!, which raises an exception when authentication fails
+ def update!(*args)
+ session = new(*args)
+ session.update!
+ end
+
+ private
+ def controllers
+ @@controllers ||= {}
+ end
+ end
+
+ attr_accessor :login_with, :remember_me, :scope
+ attr_reader :record, :unauthorized_record
+
+ # You can initialize a session by doing any of the following:
+ #
+ # UserSession.new
+ # UserSession.new(login, password)
+ # UserSession.new(:login => login, :password => password)
+ #
+ # If a user has more than one session you need to pass a scope so that Authgasm knows how to differentiate the sessions. The scope MUST be a Symbol.
+ #
+ # UserSession.new(:my_scope)
+ # UserSession.new(login, password, :my_scope)
+ # UserSession.new({:login => loing, :password => password}, :my_scope)
+ #
+ # Scopes are rarely used, but they can be useful. For example, what if users allow other users to login into their account via proxy? Now that user can "technically" be logged into 2 accounts at once.
+ # To solve this just pass a scope called :proxy, or whatever you want. Authgasm will separate everything out.s
+ def initialize(*args)
+ create_configurable_methods!
+
+ self.scope = args.pop if args.last.is_a?(Symbol)
+
+ case args.size
+ when 1
+ credentials_or_record = args.first
+ case credentials_or_record
+ when Hash
+ self.credentials = credentials_or_record
+ else
+ self.unauthorized_record = credentials_or_record
+ end
+ else
+ send("#{login_field}=", args[0])
+ send("#{password_field}=", args[1])
+ self.remember_me = args[2]
+ end
+ end
+
+ # Creates a new user session for you. It does all of the magic:
+ #
+ # 1. validates
+ # 2. sets session
+ # 3. sets cookie
+ # 4. updates magic fields
+ def create(updating = false)
+ if valid?(true)
+ cookies[cookie_key] = {
+ :value => record.send(remember_token_field),
+ :expires => remember_me? ? remember_me_for.from_now : nil
+ }
+
+ if !updating
+ record.login_count = record.login_count + 1 if record.respond_to?(:login_count)
+
+ if record.respond_to?(:current_login_at)
+ record.last_login_at = record.current_login_at if record.respond_to?(:last_login_at)
+ record.current_login_at = Time.now
+ end
+
+ if record.respond_to?(:current_login_ip)
+ record.last_login_ip = record.current_login_ip if record.respond_to?(:last_login_ip)
+ record.current_login_ip = controller.request.remote_ip
+ end
+
+ record.saving_from_session = true
+ record.save(false)
+ end
+
+ self
+ end
+ end
+
+ # Same as create but raises an exception when authentication fails
+ def create!(updating = false)
+ raise SessionInvalid.new(self) unless create(updating)
+ end
+ alias_method :start!, :create!
+
+ # Your login credentials in hash format. Usually {:login => "my login", :password => "<protected>"} depending on your configuration.
+ # Password is protected as a security measure. The raw password should never be publicly accessible.
+ def credentials
+ {login_field => send(login_field), password_field => "<Protected>"}
+ end
+
+ # Lets you set your loging and password via a hash format.
+ def credentials=(values)
+ values.symbolize_keys!
+ raise(ArgumentError, "Only 2 credentials are allowed: #{login_field} and #{password_field}") if !values.is_a?(Hash) || values.keys.size > 2 || !values.key?(login_field) || !values.key?(password_field)
+ values.each { |field, value| send("#{field}=", value) }
+ end
+
+ # Resets everything, your errors, record, cookies, and session. Basically "logs out" a user.
+ def destroy
+ errors.clear
+ @record = nil
+ cookies.delete cookie_key
+ session[session_key] = nil
+ end
+
+ # Errors when authentication fails, just like ActiveRecord errors. In fact it uses the same exact class.
+ def errors
+ @errors ||= Errors.new(self)
+ end
+
+ def inspect # :nodoc:
+ details = {}
+ case login_with
+ when :unauthorized_record
+ details[:unauthorized_record] = unauthorized_record
+ else
+ details[login_field.to_sym] = send(login_field)
+ details[password_field.to_sym] = "<protected>"
+ end
+ "#<#{self.class.name} #{details.inspect}>"
+ end
+
+ # Allows users to be remembered via a cookie.
+ def remember_me?
+ remember_me == true || remember_me = "true" || remember_me == "1"
+ end
+
+ # When to expire the cookie. See remember_me_for configuration option to change this.
+ def remember_me_until
+ remember_me_for.from_now
+ end
+
+ # Sometimes you don't want to create a session via credentials (login and password). Maybe you already have the record. Just set this record to this and it will be authenticated when you try to validate
+ # the session. Basically this is another form of credentials, you are just skipping username and password validation.
+ def unauthorized_record=(value)
+ self.login_with = :unauthorized_record
+ @unauthorized_record = value
+ end
+
+ # Updates the session with any new information. Resets the session and cookie.
+ def update
+ create(true)
+ end
+
+ # Same as update but raises an exception if validation is failed
+ def update!
+ create!(true)
+ end
+
+ def valid?(set_session = false)
+ errors.clear
+ temp_record = unauthorized_record
+
+ if login_with == :credentials
+ errors.add(login_field, "can not be blank") if login.blank?
+ errors.add(password_field, "can not be blank") if protected_password.blank?
+ return false if errors.count > 0
+
+ temp_record = klass.send(find_by_login_method, send(login_field))
+
+ if temp_record.blank?
+ errors.add(login_field, "was not found")
+ return false
+ end
+
+ unless temp_record.send(verify_password_method, protected_password)
+ errors.add(password_field, "is invalid")
+ return false
+ end
+ end
+
+ [:approved, :confirmed, :inactive].each do |required_status|
+ if temp_record.respond_to?("#{required_status}?") && !temp_record.send("#{required_status}?")
+ errors.add_to_base("Your account has not been #{required_status}")
+ return false
+ end
+ end
+
+ # All is good, lets set the record
+ @record = temp_record
+
+ # Now lets set the session to make things easier on successive requests. This is nice when logging in from a cookie, the next requests will be right from the session, which is quicker.
+ if set_session
+ session[session_key] = record.id
+ if record.class.column_names.include?("last_click_at")
+ record.last_click_at = Time.now
+ record.saving_from_session = true
+ record.save(false)
+ end
+ end
+
+ true
+ end
+
+ def valid_http_auth?(set_session = false)
+ controller.authenticate_with_http_basic do |login, password|
+ if !login.blank? && !password.blank?
+ send("#{login_method}=", login)
+ send("#{password_method}=", password)
+ return valid?(set_session)
+ end
+ end
+
+ false
+ end
+
+ def valid_cookie?(set_session = false)
+ if cookie_credentials
+ self.unauthorized_record = klass.send("find_by_#{remember_token_field}", cookie_credentials)
+ valid?(set_session)
+ end
+
+ false
+ end
+
+ def valid_session?
+ if session_credentials
+ self.unauthorized_record = klass.find_by_id(session_credentials)
+ return valid?
+ end
+
+ false
+ end
+
+ private
+ def controller
+ self.class.controller
+ end
+
+ def cookies
+ controller.send(:cookies)
+ end
+
+ def cookie_credentials
+ cookies[cookie_key]
+ end
+
+ def create_configurable_methods!
+ return if respond_to?(login_field) # already created these methods
+
+ self.class.class_eval <<-"end_eval", __FILE__, __LINE__
+ attr_reader :#{login_field}
+
+ def #{login_field}=(value)
+ self.login_with = :credentials
+ @#{login_field} = value
+ end
+
+ def #{password_field}=(value)
+ self.login_with = :credentials
+ @#{password_field} = value
+ end
+
+ def #{password_field}; end
+ end_eval
+ end
+
+ def klass
+ self.class.klass
+ end
+
+ def klass_name
+ self.class.klass_name
+ end
+
+ # The password should not be accessible publicly. This way forms using form_for don't fill the password with the attempted password. The prevent this we just create this method that is private.
+ def protected_password
+ @password
+ end
+
+ def session
+ controller.session
+ end
+
+ def session_credentials
+ session[session_key]
+ end
+ end
+ end
+end
47 lib/authgasm/session/callbacks.rb
@@ -0,0 +1,47 @@
+module Authgasm
+ module Session
+ # = Callbacks
+ #
+ # Just like in ActiveRecord you have before_save, before_validation, etc. You have similar callbacks with Authgasm, see all callbacks below.
+ module Callbacks
+ CALLBACKS = %w(before_create after_create before_destroy after_destroy before_update after_update before_validation after_validation)
+
+ def self.included(base) #:nodoc:
+ [:create, :destroy, :update, :valid?].each do |method|
+ base.send :alias_method_chain, method, :callbacks
+ end
+
+ base.send :include, ActiveSupport::Callbacks
+ base.define_callbacks *CALLBACKS
+ end
+
+ def create_with_callbacks(updating = false) # :nodoc:
+ run_callbacks(:before_create)
+ result = create_without_callbacks(updating)
+ run_callbacks(:after_create)
+ result
+ end
+
+ def destroy_with_callbacks # :nodoc:
+ run_callbacks(:before_destroy)
+ result = destroy_without_callbacks
+ run_callbacks(:after_destroy)
+ result
+ end
+
+ def update_with_callbacks # :nodoc:
+ run_callbacks(:before_update)
+ result = update_without_callbacks
+ run_callbacks(:after_update)
+ result
+ end
+
+ def valid_with_callbacks?(set_session = false) # :nodoc:
+ run_callbacks(:before_validation)
+ result = valid_without_callbacks?(set_session)
+ run_callbacks(:after_validation)
+ result
+ end
+ end
+ end
+end
193 lib/authgasm/session/config.rb
@@ -0,0 +1,193 @@
+module Authgasm
+ module Session
+ module Config # :nodoc:
+ def self.included(klass)
+ klass.extend(ClassMethods)
+ klass.send(:include, InstanceMethods)
+ end
+
+ # = Config
+ #
+ # Configuration is simple. The configuration options are just class methods. Just put this in your config/initializers directory
+ #
+ # UserSession.configure do |config|
+ # config.authenticate_with = User
+ # # ... more configuration
+ # end
+ #
+ # or you can set your configuration in the session class directly:
+ #
+ # class UserSession < Authgasm::Session::Base
+ # self.authenticate_with = User
+ # # ... more configuration
+ # end
+ #
+ # or...
+ #
+ # class UserSession < Authgasm::Session::Base
+ # configure do |config|
+ # config.authenticate_with = User
+ # # ... more configuration
+ # end
+ # end
+ #
+ # See the methods belows for all configuration options.
+ module ClassMethods
+ # Lets you change which model to use for authentication.
+ #
+ # * <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
+
+ # Convenience method that lets you easily set configuration, see examples above
+ def configure
+ yield self
+ end
+
+ # The authentication credentials are stored in a cookie as: user#{cookie_separator}crypted_password.
+ #
+ # * <tt>Default:</tt> ":::"
+ # * <tt>Accepts:</tt> String
+ def cookie_separator
+ @cookie_separator ||= ":::"
+ end
+ attr_writer :cookie_separator
+
+ # 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 scope is set it will be inserted into the beginning of the string. Exmaple:
+ #
+ # session = UserSession.new(:super_high_secret)
+ # session.cookie_key => "super_high_secret_user_credentials"
+ #
+ # * <tt>Default:</tt> "#{klass_name.underscore}_credentials"
+ # * <tt>Accepts:</tt> String
+ def cookie_key
+ @cookie_key ||= "#{klass_name.underscore}_credentials"
+ end
+ attr_writer :cookie_key
+
+ # The name of the method used to find the record by the login. What's nifty about this is that you can do anything in your method, Authgasm will just pass you the login.
+ #
+ # Let's say you allow users to login by username or email. Set this to "find_login", or whatever method you want. Then in your model create a class method like:
+ #
+ # def self.find_login(login)
+ # find_by_login(login) || find_by_email(login)
+ # end
+ #
+ # * <tt>Default:</tt> "find_by_#{login_field}"
+ # * <tt>Accepts:</tt> Symbol or String
+ def find_by_login_method
+ @find_by_login_method ||= "find_by_#{login_field}"
+ end
+ attr_writer :find_by_login_method
+
+ # The name of the method you want Authgasm to create for storing the login / username. Keep in mind this is just for your Authgasm::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> Guesses based on the model columns, tries login, username, and email. If none are present it defaults to login
+ # * <tt>Accepts:</tt> Symbol or String
+ def login_field
+ @login_field ||= (klass.columns.include?("login") && :login) || (klass.columns.include?("username") && :username) || (klass.columns.include?("email") && :email) || :login
+ end
+ attr_writer :login_field
+
+ # Works exactly like login_field, but for the password instead.
+ #
+ # * <tt>Default:</tt> Guesses based on the model columns, tries password and pass. If none are present it defaults to password
+ # * <tt>Accepts:</tt> Symbol or String
+ def password_field
+ @password_field ||= (klass.columns.include?("password") && :password) || (klass.columns.include?("pass") && :pass) || :password
+ end
+ attr_writer :password_field
+
+ # 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
+ return @remember_me_for if @set_remember_me_for
+ @remember_me_for ||= 3.months
+ end
+
+ def remember_me_for=(value) # :nodoc:
+ @set_remember_me_for = true
+ @remember_me_for = value
+ end
+
+ # The name of the field that the remember token is stored. This is for cookies. Let's say you set up your app and want all users to be remembered for 6 months. Then you realize that might be a little too
+ # long. Well they already have a cookie set to expire in 6 months. Without a token you would have to reset their password, which obviously isn't feasible. So instead of messing with their password
+ # just reset their remember token. Next time they access the site and try to login via a cookie it will be rejected and they will have to relogin.
+ #
+ # * <tt>Default:</tt> Guesses based on the model columns, tries remember_token, remember_key, cookie_token, and cookie_key. If none are present it defaults to remember_token
+ # * <tt>Accepts:</tt> Symbol or String
+ def remember_token_field
+ @remember_token_field ||=
+ (klass.columns.include?("remember_token") && :remember_token) ||
+ (klass.columns.include?("remember_key") && :remember_key) ||
+ (klass.columns.include?("cookie_token") && :cookie_token) ||
+ (klass.columns.include?("cookie_key") && :cookie_key) ||
+ :remember_token
+ end
+ attr_writer :remember_token_field
+
+ # Works exactly like cookie_key, but for sessions. See cookie_key for more info.
+ #
+ # * <tt>Default:</tt> :#{klass_name.underscore}_id
+ # * <tt>Accepts:</tt> Symbol or String
+ def session_key
+ @session_key ||= "#{klass_name.underscore}_id".to_sym
+ end
+ attr_writer :session_key
+
+ # 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_field}?"
+ # * <tt>Accepts:</tt> Symbol or String
+ def verify_password_method
+ @verify_password_method ||= "valid_#{password_field}?"
+ end
+ attr_writer :verify_password_method
+ end
+
+ module InstanceMethods # :nodoc:
+ def cookie_key
+ key_parts = [scope, self.class.cookie_key].compact
+ key_parts.join("_")
+ end
+
+ def find_by_login_method
+ self.class.find_by_login_method
+ end
+
+ def login_field
+ self.class.login_field
+ end
+
+ def password_field
+ self.class.password_field
+ end
+
+ def remember_me_for
+ self.class.remember_me_for
+ end
+
+ def remember_token_field
+ self.class.remember_token_field
+ end
+
+ def session_key
+ key_parts = [scope, self.class.session_key].compact
+ key_parts.join("_")
+ end
+
+ def verify_password_method
+ self.class.verify_password_method
+ end
+ end
+ end
+ end
+end
12 lib/authgasm/session/errors.rb
@@ -0,0 +1,12 @@
+module Authgasm
+ module Session
+ class Errors < ::ActiveRecord::Errors # :nodoc:
+ end
+
+ class SessionInvalid < ::StandardError # :nodoc:
+ def initialize(session)
+ super("Authentication failed: #{session.errors.full_messages.to_sentence}")
+ end
+ end
+ end
+end
13 lib/authgasm/sha256_crypto_provider.rb
@@ -0,0 +1,13 @@
+module Authgasm
+ # = Sha256 Crypto Provider
+ #
+ # The acts_as_authentic method allows you to pass 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 decrypt method.
+ # The password will be passed as the single parameter to each of these methods so you can do your magic.
+ #
+ # If you are encrypting via a hash just don't include a decrypt method, since hashes can't be decrypted. Authgasm will notice this adjust accordingly.
+ class Sha256CryptoProvider
+ def self.encrypt(pass)
+ Digest::SHA256.hexdigest(pass)
+ end
+ end
+end
56 lib/authgasm/version.rb
@@ -0,0 +1,56 @@
+module Authgasm # :nodoc:
+ # = Version
+ #
+ # A class for describing the current version of a library. The version
+ # consists of three parts: the +major+ number, the +minor+ number, and the
+ # +tiny+ (or +patch+) number.
+ class Version
+
+ include Comparable
+
+ # A convenience method for instantiating a new Version instance with the
+ # given +major+, +minor+, and +tiny+ components.
+ def self.[](major, minor, tiny)
+ new(major, minor, tiny)
+ end
+
+ attr_reader :major, :minor, :tiny
+
+ # Create a new Version object with the given components.
+ def initialize(major, minor, tiny)
+ @major, @minor, @tiny = major, minor, tiny
+ end
+
+ # Compare this version to the given +version+ object.
+ def <=>(version)
+ to_i <=> version.to_i
+ end
+
+ # Converts this version object to a string, where each of the three
+ # version components are joined by the '.' character. E.g., 2.0.0.
+ def to_s
+ @to_s ||= [@major, @minor, @tiny].join(".")
+ end
+
+ # Converts this version to a canonical integer that may be compared
+ # against other version objects.
+ def to_i
+ @to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
+ end
+
+ def to_a
+ [@major, @minor, @tiny]
+ end
+
+ MAJOR = 0
+ MINOR = 9
+ TINY = 0
+
+ # The current version as a Version instance
+ CURRENT = new(MAJOR, MINOR, TINY)
+ # The current version as a String
+ STRING = CURRENT.to_s
+
+ end
+
+end
10 test_app/.gitignore
@@ -0,0 +1,10 @@
+db/schema.rb
+config/database.yml
+config/locomotive.yml
+config/mongrel_cluster.yml
+tmp/*
+log/*
+private/images/*
+public/cache/*
+.DS_Store
+
256 test_app/README
@@ -0,0 +1,256 @@
+== Welcome to Rails
+
+Rails is a web-application framework that includes everything needed to create
+database-backed web applications according to the Model-View-Control pattern.
+
+This pattern splits the view (also called the presentation) into "dumb" templates
+that are primarily responsible for inserting pre-built data in between HTML tags.
+The model contains the "smart" domain objects (such as Account, Product, Person,
+Post) that holds all the business logic and knows how to persist themselves to
+a database. The controller handles the incoming requests (such as Save New Account,
+Update Product, Show Post) by manipulating the model and directing data to the view.
+
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in
+link:files/vendor/rails/activerecord/README.html.
+
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails. You can read more about Action Pack in
+link:files/vendor/rails/actionpack/README.html.
+
+
+== Getting Started
+
+1. At the command prompt, start a new Rails application using the <tt>rails</tt> command
+ and your application name. Ex: rails myapp
+2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
+3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!"
+4. Follow the guidelines to start developing your application
+
+
+== Web Servers
+
+By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise
+Rails will use WEBrick, the webserver that ships with Ruby. When you run script/server,
+Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures
+that you can always get up and running quickly.
+
+Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is
+suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
+getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
+More info at: http://mongrel.rubyforge.org
+
+If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than
+Mongrel and WEBrick and also suited for production use, but requires additional
+installation and currently only works well on OS X/Unix (Windows users are encouraged
+to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from
+http://www.lighttpd.net.
+
+And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby
+web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not
+for production.
+
+But of course its also possible to run Rails on any platform that supports FCGI.
+Apache, LiteSpeed, IIS are just a few. For more information on FCGI,
+please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI
+
+
+== Apache .htaccess example
+
+# General Apache options
+AddHandler fastcgi-script .fcgi
+AddHandler cgi-script .cgi
+Options +FollowSymLinks +ExecCGI
+
+# If you don't want Rails to look in certain directories,
+# use the following rewrite rules so that Apache won't rewrite certain requests
+#
+# Example:
+# RewriteCond %{REQUEST_URI} ^/notrails.*
+# RewriteRule .* - [L]
+
+# Redirect all requests not available on the filesystem to Rails
+# By default the cgi dispatcher is used which is very slow
+#
+# For better performance replace the dispatcher with the fastcgi one
+#
+# Example:
+# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
+RewriteEngine On
+
+# If your Rails application is accessed via an Alias directive,
+# then you MUST also set the RewriteBase in this htaccess file.
+#
+# Example:
+# Alias /myrailsapp /path/to/myrailsapp/public
+# RewriteBase /myrailsapp
+
+RewriteRule ^$ index.html [QSA]
+RewriteRule ^([^.]+)$ $1.html [QSA]
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
+
+# In case Rails experiences terminal errors
+# Instead of displaying this message you can supply a file here which will be rendered instead
+#
+# Example:
+# ErrorDocument 500 /500.html
+
+ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
+
+
+== Debugging Rails
+
+Sometimes your application goes wrong. Fortunately there are a lot of tools that
+will help you debug it and get it back on the rails.
+
+First area to check is the application log files. Have "tail -f" commands running
+on the server.log and development.log. Rails will automatically display debugging
+and runtime information to these files. Debugging info will also be shown in the
+browser on requests from 127.0.0.1.
+
+You can also log your own messages directly into the log file from your code using
+the Ruby logger class from inside your controllers. Example:
+
+ class WeblogController < ActionController::Base
+ def destroy
+ @weblog = Weblog.find(params[:id])
+ @weblog.destroy
+ logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
+ end
+ end
+
+The result will be a message in your log file along the lines of:
+
+ Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1
+
+More information on how to use the logger is at http://www.ruby-doc.org/core/
+
+Also, Ruby documentation can be found at http://www.ruby-lang.org/ including:
+
+* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/
+* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
+
+These two online (and free) books will bring you up to speed on the Ruby language
+and also on programming in general.
+
+
+== Debugger
+
+Debugger support is available through the debugger command when you start your Mongrel or
+Webrick server with --debugger. This means that you can break out of execution at any point
+in the code, investigate and change the model, AND then resume execution!
+You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'
+Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find(:all)
+ debugger
+ end
+ end
+
+So the controller will accept the action, run the first line, then present you
+with a IRB prompt in the server window. Here you can do things like:
+
+ >> @posts.inspect
+ => "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
+ #<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
+ >> @posts.first.title = "hello from a debugger"
+ => "hello from a debugger"
+
+...and even better is that you can examine how your runtime objects actually work:
+
+ >> f = @posts.first
+ => #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
+ >> f.
+ Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you enter "cont"
+
+
+== Console
+
+You can interact with the domain model by starting the console through <tt>script/console</tt>.
+Here you'll have all parts of the application configured, just like it is when the
+application is running. You can inspect domain models, change values, and save to the
+database. Starting the script without arguments will launch it in the development environment.
+Passing an argument will specify a different environment, like <tt>script/console production</tt>.
+
+To reload your controllers and models after launching the console run <tt>reload!</tt>
+
+== dbconsole
+
+You can go to the command line of your database directly through <tt>script/dbconsole</tt>.
+You would be connected to the database with the credentials defined in database.yml.
+Starting the script without arguments will connect you to the development database. Passing an
+argument will connect you to a different database, like <tt>script/dbconsole production</tt>.
+Currently works for mysql, postgresql and sqlite.
+
+== Description of Contents
+
+app
+ Holds all the code that's specific to this particular application.
+
+app/controllers
+ Holds controllers that should be named like weblogs_controller.rb for
+ automated URL mapping. All controllers should descend from ApplicationController
+ which itself descends from ActionController::Base.
+
+app/models
+ Holds models that should be named like post.rb.
+ Most models will descend from ActiveRecord::Base.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby
+ syntax.
+
+app/views/layouts
+ Holds the template files for layouts to be used with views. This models the common
+ header/footer method of wrapping views. In your views, define a layout using the
+ <tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,
+ call <% yield %> to render the view using this layout.
+
+app/helpers
+ Holds view helpers that should be named like weblogs_helper.rb. These are generated
+ for you automatically when using script/generate for controllers. Helpers can be used to
+ wrap functionality for your views into methods.
+
+config
+ Configuration files for the Rails environment, the routing map, the database, and other dependencies.
+
+db
+ Contains the database schema in schema.rb. db/migrate contains all
+ the sequence of Migrations for your schema.
+
+doc
+ This directory is where your application documentation will be stored when generated
+ using <tt>rake doc:app</tt>
+
+lib
+ Application specific libraries. Basically, any kind of custom code that doesn't
+ belong under controllers, models, or helpers. This directory is in the load path.
+
+public
+ The directory available for the web server. Contains subdirectories for images, stylesheets,
+ and javascripts. Also contains the dispatchers and the default HTML files. This should be
+ set as the DOCUMENT_ROOT of your web server.
+
+script
+ Helper scripts for automation and generation.
+
+test
+ Unit and functional tests along with fixtures. When using the script/generate scripts, template
+ test files will be generated for you and placed in this directory.
+
+vendor
+ External libraries that the application depends on. Also includes the plugins subdirectory.
+ If the app has frozen rails, those gems also go here, under vendor/rails/.
+ This directory is in the load path.
10 test_app/Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
+
+require(File.join(File.dirname(__FILE__), 'config', 'boot'))
+
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+require 'tasks/rails'
46 test_app/app/controllers/application.rb
@@ -0,0 +1,46 @@
+class ApplicationController < ActionController::Base
+ helper :all # include all helpers, all the time
+ protect_from_forgery # :secret => '3e944977657f54e55cb20d83a418ff65'
+ filter_parameter_logging :password, :confirm_password
+
+ before_filter :load_current_user
+
+ private
+ def load_current_user
+ @user_session = UserSession.find
+ @current_user = @user_session && @user_session.record
+ end
+
+ def require_user
+ unless @current_user
+ store_location
+ flash[:notice] = "You must be logged in to access this page"
+ redirect_to new_user_session_url
+ return false
+ end
+ end
+
+ def require_no_user
+ if @current_user
+ store_location
+ flash[:notice] = "You must be logged out to access this page"
+ redirect_to account_url
+ return false
+ end
+ end
+
+ def prevent_store_location
+ @prevent_store_location = true
+ end
+
+ def store_location
+ return if @prevent_store_location == true
+ session[:return_to] = request.request_uri
+ end
+
+ def redirect_back_or_default(default)
+ raise (session[:return_to] || default).inspect if (session[:return_to] || default) == nil
+ redirect_to(session[:return_to] || default)
+ session[:return_to] = nil
+ end
+end
25 test_app/app/controllers/user_sessions_controller.rb
@@ -0,0 +1,25 @@
+class UserSessionsController < ApplicationController
+ before_filter :prevent_store_location, :only => [:destroy, :create]
+ before_filter :require_no_user, :only => [:new, :create]
+ before_filter :require_user, :only => :destroy
+
+ def new
+ @user_session = UserSession.new
+ end
+
+ def create
+ @user_session = UserSession.new(params[:user_session])
+ if @user_session.create
+ flash[:notice] = "Login successful!"
+ redirect_back_or_default(account_url)
+ else
+ render :action => :new
+ end
+ end
+
+ def destroy
+ @user_session.destroy
+ flash[:notice] = "Logout successful!"
+ redirect_back_or_default(new_user_session_url)
+ end
+end
37 test_app/app/controllers/users_controller.rb
@@ -0,0 +1,37 @@
+class UsersController < ApplicationController
+ before_filter :require_no_user, :only => [:new, :create]
+ before_filter :require_user, :only => [:show, :edit, :update]
+
+ def new
+ @user = User.new
+ end
+
+ def create
+ @user = User.new(params[:user])
+ if @user.save
+ flash[:notice] = "Account registered!"
+ redirect_to account_path
+ else
+ render :action => :new
+ end
+ end
+
+ def show
+ @user = @current_user
+ end
+
+ def edit
+ @user = @current_user
+ end
+
+ def update
+ @user = @current_user
+ @user.attributes = params[:user]
+ if @user.save
+ flash[:notice] = "Account updated!"
+ redirect_to account_path
+ else
+ render :action => :edit
+ end
+ end
+end
3  test_app/app/helpers/application_helper.rb
@@ -0,0 +1,3 @@
+# Methods added to this helper will be available to all templates in the application.
+module ApplicationHelper
+end
2  test_app/app/helpers/user_sessions_helper.rb
@@ -0,0 +1,2 @@
+module UserSessionsHelper
+end
2  test_app/app/helpers/users_helper.rb
@@ -0,0 +1,2 @@
+module UsersHelper
+end
3  test_app/app/models/user.rb
@@ -0,0 +1,3 @@
+class User < ActiveRecord::Base
+ acts_as_authentic
+end
3  test_app/app/models/user_session.rb
@@ -0,0 +1,3 @@
+class UserSession < Authgasm::Session::Base
+
+end
12 test_app/app/views/asses/edit.html.erb
@@ -0,0 +1,12 @@
+<h1>Editing ass</h1>
+
+<% form_for(@ass) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
+
+<%= link_to 'Show', @ass %> |
+<%= link_to 'Back', asses_path %>
18 test_app/app/views/asses/index.html.erb
@@ -0,0 +1,18 @@
+<h1>Listing asses</h1>
+
+<table>
+ <tr>
+ </tr>
+
+<% for ass in @asses %>
+ <tr>
+ <td><%= link_to 'Show', ass %></td>
+ <td><%= link_to 'Edit', edit_ass_path(ass) %></td>
+ <td><%= link_to 'Destroy', ass, :confirm => 'Are you sure?', :method => :delete %></td>
+ </tr>
+<% end %>
+</table>
+
+<br />
+
+<%= link_to 'New ass', new_ass_path %>
11 test_app/app/views/asses/new.html.erb
@@ -0,0 +1,11 @@
+<h1>New ass</h1>
+
+<% form_for(@ass) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.submit "Create" %>
+ </p>
+<% end %>
+
+<%= link_to 'Back', asses_path %>
3  test_app/app/views/asses/show.html.erb
@@ -0,0 +1,3 @@
+
+<%= link_to 'Edit', edit_ass_path(@ass) %> |
+<%= link_to 'Back', asses_path %>
25 test_app/app/views/layouts/application.html.erb
@@ -0,0 +1,25 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
+ <title><%= controller.controller_name %>: <%= controller.action_name %></title>
+ <%= stylesheet_link_tag 'scaffold' %>
+</head>
+<body>
+
+<% if !@current_user %>
+ <%= link_to "Register", new_user_path %> |
+ <%= link_to "Log In", new_user_session_path %>
+<% else %>
+ <%= link_to "My Account", account_path %> |
+ <%= link_to "Logout", logout_path, :confirm => "Are you sure you want to logout?" %>
+<% end %>
+
+<p style="color: green"><%= flash[:notice] %></p>
+
+<%= yield %>
+
+</body>
+</html>
13 test_app/app/views/user_sessions/new.html.erb
@@ -0,0 +1,13 @@
+<h1>Login</h1>
+
+<%= error_messages_for "user_session", :header_message => nil %>
+
+<% form_for @user_session do |f| %>
+ <%= f.label :login %><br />
+ <%= f.text_field :login %><br />
+ <br />
+ <%= f.label :password %><br />
+ <%= f.password_field :password %><br />
+ <br />
+ <%= f.submit "Login" %>
+<% end %>
15 test_app/app/views/users/_form.erb
@@ -0,0 +1,15 @@
+<%= form.label :login %><br />
+<%= form.text_field :login %><br />
+<br />
+<%= form.label :password, form.object.new_record? ? nil : "Change password" %><br />
+<%= form.password_field :password %><br />
+<br />
+<%= form.label :confirm_password%><br />
+<%= form.password_field :confirm_password %><br />
+<br />
+<%= form.label :first_name %><br />
+<%= form.text_field :first_name %><br />
+<br />
+<%= form.label :last_name %><br />
+<%= form.text_field :last_name %><br />
+<br />
8 test_app/app/views/users/edit.html.erb
@@ -0,0 +1,8 @@
+<h1>Edit My Account</h1>
+
+<%= error_messages_for "user" %>
+
+<% form_for @user do |f| %>
+ <%= render :partial => "form", :object => f %>
+ <%= f.submit "Update" %>
+<% end %>
8 test_app/app/views/users/new.html.erb
@@ -0,0 +1,8 @@
+<h1>Register</h1>
+
+<%= error_messages_for "user" %>
+
+<% form_for @user do |f| %>
+ <%= render :partial => "form", :object => f %>
+ <%= f.submit "Register" %>
+<% end %>
19 test_app/app/views/users/show.html.erb
@@ -0,0 +1,19 @@
+<h1><%= @current_user.login %></h1>
+
+<table>
+ <tr>
+ <td>Login:</td>
+ <td><%= @current_user.login %></td>
+ </tr>
+ <tr>
+ <td>First name:</td>
+ <td><%= @current_user.first_name %></td>
+ </tr>
+ <tr>
+ <td>Last name:</td>
+ <td><%= @current_user.last_name %></td>
+ </tr>
+</table>
+<br />
+
+<%= link_to "Edit", edit_account_path %><br />
109 test_app/config/boot.rb
@@ -0,0 +1,109 @@
+# Don't change this file!
+# Configure your app in config/environment.rb and config/environments/*.rb
+
+RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
+
+module Rails
+ class << self
+ def boot!
+ unless booted?
+ preinitialize
+ pick_boot.run
+ end
+ end
+
+ def booted?
+ defined? Rails::Initializer
+ end
+
+ def pick_boot
+ (vendor_rails? ? VendorBoot : GemBoot).new
+ end
+
+ def vendor_rails?
+ File.exist?("#{RAILS_ROOT}/vendor/rails")
+ end
+
+ def preinitialize
+ load(preinitializer_path) if File.exist?(preinitializer_path)
+ end
+
+ def preinitializer_path
+ "#{RAILS_ROOT}/config/preinitializer.rb"
+ end
+ end
+
+ class Boot
+ def run
+ load_initializer
+ Rails::Initializer.run(:set_load_path)
+ end
+ end
+
+ class VendorBoot < Boot
+ def load_initializer
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+ Rails::Initializer.run(:install_gem_spec_stubs)
+ end
+ end
+
+ class GemBoot < Boot
+ def load_initializer
+ self.class.load_rubygems
+ load_rails_gem
+ require 'initializer'
+ end
+
+ def load_rails_gem
+ if version = self.class.gem_version
+ gem 'rails', version
+ else
+ gem 'rails'
+ end
+ rescue Gem::LoadError => load_error
+ $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
+ exit 1
+ end
+
+ class << self
+ def rubygems_version
+ Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion
+ end
+
+ def gem_version
+ if defined? RAILS_GEM_VERSION
+ RAILS_GEM_VERSION
+ elsif ENV.include?('RAILS_GEM_VERSION')
+ ENV['RAILS_GEM_VERSION']
+ else
+ parse_gem_version(read_environment_rb)
+ end
+ end
+
+ def load_rubygems
+ require 'rubygems'
+ min_version = '1.1.1'
+ unless rubygems_version >= min_version
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
+ exit 1
+ end
+
+ rescue LoadError
+ $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
+ exit 1
+ end
+
+ def parse_gem_version(text)
+ $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
+ end
+
+ private
+ def read_environment_rb
+ File.read("#{RAILS_ROOT}/config/environment.rb")
+ end
+ end
+ end
+end
+
+# All that for this:
+Rails.boot!
69 test_app/config/environment.rb
@@ -0,0 +1,69 @@
+# Be sure to restart your server when you modify this file
+
+# Uncomment below to force Rails into production mode when
+# you don't control web/app server and can't set it the proper way
+# ENV['RAILS_ENV'] ||= 'production'
+
+# Specifies gem version of Rails to use when vendor/rails is not present
+RAILS_GEM_VERSION = '2.1.1' unless defined? RAILS_GEM_VERSION
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+ # See Rails::Configuration for more options.
+
+ # Skip frameworks you're not going to use. To use Rails without a database
+ # you must remove the Active Record framework.
+ # config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
+
+ # Specify gems that this application depends on.
+ # They can then be installed with "rake gems:install" on new installations.
+ # config.gem "bj"
+ # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
+ # config.gem "aws-s3", :lib => "aws/s3"
+
+ # Only load the plugins named here, in the order given. By default, all plugins
+ # in vendor/plugins are loaded in alphabetical order.
+ # :all can be used as a placeholder for all plugins not explicitly named
+ # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
+ config.plugin_paths += ["#{RAILS_ROOT}/../.."]
+ config.plugins = [:authgasm]
+
+ # Add additional load paths for your own custom dirs
+ # config.load_paths += %W( #{RAILS_ROOT}/extras )
+
+ # Force all environments to use the same logger level
+ # (by default production uses :info, the others :debug)
+ # config.log_level = :debug
+
+ # Make Time.zone default to the specified zone, and make Active Record store time values
+ # in the database in UTC, and return them converted to the specified local zone.
+ # Run "rake -D time" for a list of tasks for finding time zone names. Comment line to use default local time.
+ config.time_zone = 'UTC'
+
+ # Your secret key for verifying cookie session data integrity.
+ # If you change this key, all old sessions will become invalid!
+ # Make sure the secret is at least 30 characters and all random,
+ # no regular words or you'll be exposed to dictionary attacks.
+ config.action_controller.session = {
+ :session_key => '_test_app_session',
+ :secret => '2077420310120803c5ab6afbe99b0f51e1e9c6fd2bc931920dd5b33c1526c889ef379d31f7d87c31878c3356aaf020d1b541c40567e870ff4e363bd34b73fb8b'
+ }
+
+ # Use the database for sessions instead of the cookie-based default,
+ # which shouldn't be used to store highly confidential information
+ # (create the session table with "rake db:sessions:create")
+ # config.action_controller.session_store = :active_record_store
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Activate observers that should always be running
+ #config.active_record.observers = [:user_observer]
+end
17 test_app/config/environments/development.rb
@@ -0,0 +1,17 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# In the development environment your application's code is reloaded on
+# every request. This slows down response time but is perfect for development
+# since you don't have to restart the webserver when you make code changes.
+config.cache_classes = false
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_view.debug_rjs = true
+config.action_controller.perform_caching = false
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
22 test_app/config/environments/production.rb
@@ -0,0 +1,22 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The production environment is meant for finished, "live" apps.
+# Code is not reloaded between requests
+config.cache_classes = true
+
+# Use a different logger for distributed setups
+# config.logger = SyslogLogger.new
+
+# Full error reports are disabled and caching is turned on
+config.action_controller.consider_all_requests_local = false
+config.action_controller.perform_caching = true
+config.action_view.cache_template_loading = true
+
+# Use a different cache store in production
+# config.cache_store = :mem_cache_store
+
+# Enable serving of images, stylesheets, and javascripts from an asset server
+# config.action_controller.asset_host = "http://assets.example.com"
+
+# Disable delivery errors, bad email addresses will be ignored
+# config.action_mailer.raise_delivery_errors = false
22 test_app/config/environments/test.rb
@@ -0,0 +1,22 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The test environment is used exclusively to run your application's
+# test suite. You never need to work with it otherwise. Remember that
+# your test database is "scratch space" for the test suite and is wiped
+# and recreated between test runs. Don't rely on the data there!
+config.cache_classes = true
+
+# Log error messages when you accidentally call methods on nil.
+config.whiny_nils = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching = false
+
+# Disable request forgery protection in test environment
+config.action_controller.allow_forgery_protection = false
+
+# Tell Action Mailer not to deliver emails to the real world.
+# The :test delivery method accumulates sent emails in the
+# ActionMailer::Base.deliveries array.
+config.action_mailer.delivery_method = :test
10 test_app/config/initializers/inflections.rb
@@ -0,0 +1,10 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format
+# (all these examples are active by default):
+# ActiveSupport::Inflector.inflections do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
5 test_app/config/initializers/mime_types.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
+# Mime::Type.register_alias "text/html", :iphone
17 test_app/config/initializers/new_rails_defaults.rb
@@ -0,0 +1,17 @@
+# These settings change the behavior of Rails 2 apps and will be defaults
+# for Rails 3. You can remove this initializer when Rails 3 is released.
+
+if defined?(ActiveRecord)
+ # Include Active Record class name as root for JSON serialized output.
+ ActiveRecord::Base.include_root_in_json = true
+
+ # Store the full class name (including module namespace) in STI type column.
+ ActiveRecord::Base.store_full_sti_class = true
+end
+
+# Use ISO 8601 format for JSON serialized times and dates.
+ActiveSupport.use_standard_json_time_format = true
+
+# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
+# if you're including raw json in an HTML page.
+ActiveSupport.escape_html_entities_in_json = false
7 test_app/config/routes.rb
@@ -0,0 +1,7 @@
+ActionController::Routing::Routes.draw do |map|
+ map.resources :users
+ map.resources :user_sessions
+ map.resource :account, :controller => "users"
+ map.logout "/logout", :controller => "user_sessions", :action => "destroy"
+ map.default "/", :controller => "user_sessions", :action => "new"
+end
BIN  test_app/db/development.sqlite3
Binary file not shown
17 test_app/db/migrate/20081023040052_create_users.rb
@@ -0,0 +1,17 @@
+class CreateUsers < ActiveRecord::Migration
+ def self.up
+ create_table :users do |t|
+ t.timestamps
+ t.string :login, :null => false
+ t.string :crypted_password
+ t.string :password_salt
+ t.string :remember_token
+ t.string :first_name
+ t.string :last_name
+ end
+ end
+
+ def self.down
+ drop_table :users
+ end
+end
BIN  test_app/db/test.sqlite3
Binary file not shown
2  test_app/doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
30 test_app/public/404.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>The page you were looking for doesn't exist (404)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/404.html -->
+ <div class="dialog">
+ <h1>The page you were looking for doesn't exist.</h1>
+ <p>You may have mistyped the address or the page may have moved.</p>
+ </div>
+</body>
+</html>
30 test_app/public/422.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+
+<head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
+ <title>The change you wanted was rejected (422)</title>
+ <style type="text/css">
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
+ div.dialog {
+ width: 25em;
+ padding: 0 4em;
+ margin: 4em auto 0 auto;
+ border: 1px solid #ccc;
+ border-right-color: #999;
+ border-bottom-color: #999;
+ }
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
+ </style>
+</head>
+
+<body>
+ <!-- This file lives in public/422.html -->
+ <div class="dialog">
+ <h1>The change you wanted was rejected.</h1>