Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

* added restful-authentication

* updated users model to include psn handle, avatar url, and timezone
  • Loading branch information...
commit 91bc41ca284ba4f9f45f0738722c35eb1d8b8bf1 1 parent d6835b9
@hone authored
Showing with 6,051 additions and 1 deletion.
  1. +1 −0  .gitignore
  2. +8 −0 app/controllers/application.rb
  3. +43 −0 app/controllers/sessions_controller.rb
  4. +85 −0 app/controllers/users_controller.rb
  5. +2 −0  app/helpers/sessions_helper.rb
  6. +93 −0 app/helpers/users_helper.rb
  7. +56 −0 app/models/user.rb
  8. +16 −0 app/views/sessions/new.html.erb
  9. +8 −0 app/views/users/_user_bar.html.erb
  10. +39 −0 app/views/users/edit.html.erb
  11. +19 −0 app/views/users/new.html.erb
  12. +25 −0 app/views/users/show.html.erb
  13. +2 −1  config/environment.rb
  14. +38 −0 config/initializers/site_keys.rb
  15. +8 −0 config/routes.rb
  16. +20 −0 db/migrate/20081215014407_create_users.rb
  17. +17 −0 db/migrate/20081215050613_add_fields_to_users.rb
  18. +189 −0 lib/authenticated_system.rb
  19. +11 −0 lib/authenticated_test_helper.rb
  20. +31 −0 test/fixtures/users.yml
  21. +82 −0 test/functional/sessions_controller_test.rb
  22. +61 −0 test/functional/users_controller_test.rb
  23. +103 −0 test/unit/user_test.rb
  24. +21 −0 vendor/plugins/restful-authentication/.gitignore
  25. +68 −0 vendor/plugins/restful-authentication/CHANGELOG
  26. +224 −0 vendor/plugins/restful-authentication/README.textile
  27. +32 −0 vendor/plugins/restful-authentication/Rakefile
  28. +15 −0 vendor/plugins/restful-authentication/TODO
  29. +1 −0  vendor/plugins/restful-authentication/generators/authenticated/USAGE
  30. +478 −0 vendor/plugins/restful-authentication/generators/authenticated/authenticated_generator.rb
  31. +54 −0 vendor/plugins/restful-authentication/generators/authenticated/lib/insert_routes.rb
  32. +8 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/_model_partial.html.erb
  33. +3 −0  vendor/plugins/restful-authentication/generators/authenticated/templates/activation.erb
  34. +189 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/authenticated_system.rb
  35. +22 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/authenticated_test_helper.rb
  36. +43 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/controller.rb
  37. +2 −0  vendor/plugins/restful-authentication/generators/authenticated/templates/helper.rb
  38. +16 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/login.html.erb
  39. +25 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/mailer.rb
  40. +26 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/migration.rb
  41. +83 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/model.rb
  42. +85 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/model_controller.rb
  43. +93 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/model_helper.rb
  44. +158 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/model_helper_spec.rb
  45. +11 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/observer.rb
  46. +19 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/signup.html.erb
  47. +8 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/signup_notification.erb
  48. +38 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/site_keys.rb
  49. +90 −0 ...r/plugins/restful-authentication/generators/authenticated/templates/spec/controllers/access_control_spec.rb
  50. +102 −0 ...ins/restful-authentication/generators/authenticated/templates/spec/controllers/authenticated_system_spec.rb
  51. +139 −0 ...gins/restful-authentication/generators/authenticated/templates/spec/controllers/sessions_controller_spec.rb
  52. +198 −0 ...plugins/restful-authentication/generators/authenticated/templates/spec/controllers/users_controller_spec.rb
  53. +60 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/spec/fixtures/users.yml
  54. +141 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/spec/helpers/users_helper_spec.rb
  55. +290 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/spec/models/user_spec.rb
  56. +22 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/rest_auth_stories.rb
  57. +81 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/rest_auth_stories_helper.rb
  58. +49 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/steps/ra_navigation_steps.rb
  59. +179 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/steps/ra_resource_steps.rb
  60. +171 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/steps/ra_response_steps.rb
  61. +153 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/steps/user_steps.rb
  62. +186 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/users/accounts.story
  63. +134 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/stories/users/sessions.story
  64. +82 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/test/functional_test.rb
  65. +31 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/test/mailer_test.rb
  66. +93 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/test/model_functional_test.rb
  67. +164 −0 vendor/plugins/restful-authentication/generators/authenticated/templates/test/unit_test.rb
  68. +1 −0  vendor/plugins/restful-authentication/init.rb
  69. +1 −0  vendor/plugins/restful-authentication/install.rb
  70. +40 −0 vendor/plugins/restful-authentication/lib/authentication.rb
  71. +82 −0 vendor/plugins/restful-authentication/lib/authentication/by_cookie_token.rb
  72. +64 −0 vendor/plugins/restful-authentication/lib/authentication/by_password.rb
  73. +14 −0 vendor/plugins/restful-authentication/lib/authorization.rb
  74. +63 −0 vendor/plugins/restful-authentication/lib/authorization/aasm_roles.rb
  75. +62 −0 vendor/plugins/restful-authentication/lib/authorization/stateful_roles.rb
  76. +14 −0 vendor/plugins/restful-authentication/lib/trustification.rb
  77. +20 −0 vendor/plugins/restful-authentication/lib/trustification/email_validation.rb
  78. +2 −0  vendor/plugins/restful-authentication/notes/AccessControl.txt
  79. +5 −0 vendor/plugins/restful-authentication/notes/Authentication.txt
  80. +154 −0 vendor/plugins/restful-authentication/notes/Authorization.txt
  81. +78 −0 vendor/plugins/restful-authentication/notes/RailsPlugins.txt
  82. BIN  vendor/plugins/restful-authentication/notes/SecurityFramework.graffle
  83. BIN  vendor/plugins/restful-authentication/notes/SecurityFramework.png
  84. +163 −0 vendor/plugins/restful-authentication/notes/SecurityPatterns.txt
  85. +126 −0 vendor/plugins/restful-authentication/notes/Tradeoffs.txt
  86. +49 −0 vendor/plugins/restful-authentication/notes/Trustification.txt
  87. +3 −0  vendor/plugins/restful-authentication/rails/init.rb
  88. +33 −0 vendor/plugins/restful-authentication/restful-authentication.gemspec
  89. +33 −0 vendor/plugins/restful-authentication/tasks/auth.rake
View
1  .gitignore
@@ -1,3 +1,4 @@
+db/schema.rb
log/*.log
tmp/**/*
config/database.yml
View
8 app/controllers/application.rb
@@ -2,8 +2,11 @@
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
+ include AuthenticatedSystem
helper :all # include all helpers, all the time
+ before_filter :set_user_time_zone
+
# See ActionController::RequestForgeryProtection for details
# Uncomment the :secret if you're not using the cookie session store
protect_from_forgery # :secret => '22ef26b318e5de96c5d3951388ef021a'
@@ -12,4 +15,9 @@ class ApplicationController < ActionController::Base
# Uncomment this to filter the contents of submitted sensitive data parameters
# from your application log (in this case, all fields with names like "password").
# filter_parameter_logging :password
+
+ private
+ def set_user_time_zone
+ Time.zone = current_user.time_zone if logged_in?
+ end
end
View
43 app/controllers/sessions_controller.rb
@@ -0,0 +1,43 @@
+# This controller handles the login/logout function of the site.
+class SessionsController < ApplicationController
+ # Be sure to include AuthenticationSystem in Application Controller instead
+ include AuthenticatedSystem
+
+ # render new.rhtml
+ def new
+ end
+
+ def create
+ logout_keeping_session!
+ user = User.authenticate(params[:login], params[:password])
+ if user
+ # Protects against session fixation attacks, causes request forgery
+ # protection if user resubmits an earlier form using back
+ # button. Uncomment if you understand the tradeoffs.
+ # reset_session
+ self.current_user = user
+ new_cookie_flag = (params[:remember_me] == "1")
+ handle_remember_cookie! new_cookie_flag
+ redirect_back_or_default('/')
+ flash[:notice] = "Logged in successfully"
+ else
+ note_failed_signin
+ @login = params[:login]
+ @remember_me = params[:remember_me]
+ render :action => 'new'
+ end
+ end
+
+ def destroy
+ logout_killing_session!
+ flash[:notice] = "You have been logged out."
+ redirect_back_or_default('/')
+ end
+
+protected
+ # Track failed login attempts
+ def note_failed_signin
+ flash[:error] = "Couldn't log you in as '#{params[:login]}'"
+ logger.warn "Failed login for '#{params[:login]}' from #{request.remote_ip} at #{Time.now.utc}"
+ end
+end
View
85 app/controllers/users_controller.rb
@@ -0,0 +1,85 @@
+class UsersController < ApplicationController
+ WHITELIST = [:show, :edit]
+ before_filter :login_required, :except => WHITELIST
+ before_filter( :except => WHITELIST ) {|c| c.send( "id_match_current_user?", c.action_name, c.params[:id] ) }
+
+ # render new.rhtml
+ def new
+ @user = User.new
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => @user }
+ end
+ end
+
+ def edit
+ @user = User.find( params[:id] )
+ @time_zones = ActiveSupport::TimeZone.us_zones
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => clean( @user ) }
+ end
+ end
+
+ def update
+ @user = User.find( params[:id] )
+
+ respond_to do |format|
+ if @user.update_attributes( params[:user] )
+ flash[:notice] = 'Profile successfully updated.'
+ format.html { redirect_to( @user ) }
+ format.xml { head :ok }
+ else
+ format.html { render :action => 'edit' }
+ format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ end
+ end
+ end
+
+ def show
+ @user = User.find( params[:id] )
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => clean( @user ) }
+ end
+ end
+
+ def create
+ logout_keeping_session!
+ @user = User.new(params[:user])
+ success = @user && @user.save
+ if success && @user.errors.empty?
+ # Protects against session fixation attacks, causes request forgery
+ # protection if visitor resubmits an earlier form using back
+ # button. Uncomment if you understand the tradeoffs.
+ # reset session
+ self.current_user = @user # !! now logged in
+ redirect_back_or_default('/')
+ flash[:notice] = "Thanks for signing up! We're sending you an email with your activation code."
+ else
+ flash[:error] = "We couldn't set up that account, sorry. Please try again, or contact an admin (link is above)."
+ render :action => 'new'
+ end
+ end
+
+ private
+ # cleans the user object for rendering to xml
+ # changes sensitive information to nil
+ def clean( user )
+ user.crypted_password = nil
+ user.salt = nil
+ user.remember_token = nil
+
+ user
+ end
+
+ # authorization check to make sure the id matches the user's id
+ def id_match_current_user?( action_name, id )
+ if( action_name )
+ logged_in? and @current_user.id == id
+ end
+ end
+end
View
2  app/helpers/sessions_helper.rb
@@ -0,0 +1,2 @@
+module SessionsHelper
+end
View
93 app/helpers/users_helper.rb
@@ -0,0 +1,93 @@
+module UsersHelper
+
+ #
+ # Use this to wrap view elements that the user can't access.
+ # !! Note: this is an *interface*, not *security* feature !!
+ # You need to do all access control at the controller level.
+ #
+ # Example:
+ # <%= if_authorized?(:index, User) do link_to('List all users', users_path) end %> |
+ # <%= if_authorized?(:edit, @user) do link_to('Edit this user', edit_user_path) end %> |
+ # <%= if_authorized?(:destroy, @user) do link_to 'Destroy', @user, :confirm => 'Are you sure?', :method => :delete end %>
+ #
+ #
+ def if_authorized?(action, resource, &block)
+ if authorized?(action, resource)
+ yield action, resource
+ end
+ end
+
+ #
+ # Link to user's page ('users/1')
+ #
+ # By default, their login is used as link text and link title (tooltip)
+ #
+ # Takes options
+ # * :content_text => 'Content text in place of user.login', escaped with
+ # the standard h() function.
+ # * :content_method => :user_instance_method_to_call_for_content_text
+ # * :title_method => :user_instance_method_to_call_for_title_attribute
+ # * as well as link_to()'s standard options
+ #
+ # Examples:
+ # link_to_user @user
+ # # => <a href="/users/3" title="barmy">barmy</a>
+ #
+ # # if you've added a .name attribute:
+ # content_tag :span, :class => :vcard do
+ # (link_to_user user, :class => 'fn n', :title_method => :login, :content_method => :name) +
+ # ': ' + (content_tag :span, user.email, :class => 'email')
+ # end
+ # # => <span class="vcard"><a href="/users/3" title="barmy" class="fn n">Cyril Fotheringay-Phipps</a>: <span class="email">barmy@blandings.com</span></span>
+ #
+ # link_to_user @user, :content_text => 'Your user page'
+ # # => <a href="/users/3" title="barmy" class="nickname">Your user page</a>
+ #
+ def link_to_user(user, options={})
+ raise "Invalid user" unless user
+ options.reverse_merge! :content_method => :login, :title_method => :login, :class => :nickname
+ content_text = options.delete(:content_text)
+ content_text ||= user.send(options.delete(:content_method))
+ options[:title] ||= user.send(options.delete(:title_method))
+ link_to h(content_text), user_path(user), options
+ end
+
+ #
+ # Link to login page using remote ip address as link content
+ #
+ # The :title (and thus, tooltip) is set to the IP address
+ #
+ # Examples:
+ # link_to_login_with_IP
+ # # => <a href="/login" title="169.69.69.69">169.69.69.69</a>
+ #
+ # link_to_login_with_IP :content_text => 'not signed in'
+ # # => <a href="/login" title="169.69.69.69">not signed in</a>
+ #
+ def link_to_login_with_IP content_text=nil, options={}
+ ip_addr = request.remote_ip
+ content_text ||= ip_addr
+ options.reverse_merge! :title => ip_addr
+ if tag = options.delete(:tag)
+ content_tag tag, h(content_text), options
+ else
+ link_to h(content_text), login_path, options
+ end
+ end
+
+ #
+ # Link to the current user's page (using link_to_user) or to the login page
+ # (using link_to_login_with_IP).
+ #
+ def link_to_current_user(options={})
+ if current_user
+ link_to_user current_user, options
+ else
+ content_text = options.delete(:content_text) || 'not signed in'
+ # kill ignored options from link_to_user
+ [:content_method, :title_method].each{|opt| options.delete(opt)}
+ link_to_login_with_IP content_text, options
+ end
+ end
+
+end
View
56 app/models/user.rb
@@ -0,0 +1,56 @@
+require 'digest/sha1'
+
+class User < ActiveRecord::Base
+ include Authentication
+ include Authentication::ByPassword
+ include Authentication::ByCookieToken
+
+ validates_presence_of :login
+ validates_length_of :login, :within => 3..40
+ validates_uniqueness_of :login
+ validates_format_of :login, :with => Authentication.login_regex, :message => Authentication.bad_login_message
+
+ validates_format_of :name, :with => Authentication.name_regex, :message => Authentication.bad_name_message, :allow_nil => true
+ validates_length_of :name, :maximum => 100
+
+ validates_presence_of :email
+ validates_length_of :email, :within => 6..100 #r@a.wk
+ validates_uniqueness_of :email
+ validates_format_of :email, :with => Authentication.email_regex, :message => Authentication.bad_email_message
+ validates_presence_of :psn
+ validates_uniqueness_of :psn
+
+
+
+ # HACK HACK HACK -- how to do attr_accessible from here?
+ # prevents a user from submitting a crafted form that bypasses activation
+ # anything else you want your user to change should be added here.
+ attr_accessible :login, :email, :name, :password, :password_confirmation, :time_zone, :psn, :avatar_url
+
+
+
+ # Authenticates a user by their login name and unencrypted password. Returns the user or nil.
+ #
+ # uff. this is really an authorization, not authentication routine.
+ # We really need a Dispatch Chain here or something.
+ # This will also let us return a human error message.
+ #
+ def self.authenticate(login, password)
+ return nil if login.blank? || password.blank?
+ u = find_by_login(login) # need to get the salt
+ u && u.authenticated?(password) ? u : nil
+ end
+
+ def login=(value)
+ write_attribute :login, (value ? value.downcase : nil)
+ end
+
+ def email=(value)
+ write_attribute :email, (value ? value.downcase : nil)
+ end
+
+ protected
+
+
+
+end
View
16 app/views/sessions/new.html.erb
@@ -0,0 +1,16 @@
+<h1>Log In</h1>
+
+<% form_tag session_path do -%>
+<p><%= label_tag 'login' %><br />
+<%= text_field_tag 'login', @login %></p>
+
+<p><%= label_tag 'password' %><br/>
+<%= password_field_tag 'password', nil %></p>
+
+<!-- Uncomment this if you want this functionality
+<p><%= label_tag 'remember_me', 'Remember me' %>
+<%= check_box_tag 'remember_me', '1', @remember_me %></p>
+-->
+
+<p><%= submit_tag 'Log in' %></p>
+<% end -%>
View
8 app/views/users/_user_bar.html.erb
@@ -0,0 +1,8 @@
+<% if logged_in? -%>
+ <div id="user-bar-greeting">Logged in as <%= link_to_current_user :content_method => :login %></div>
+ <div id="user-bar-action" >(<%= link_to "Log out", logout_path, { :title => "Log out" } %>)</div>
+<% else -%>
+ <div id="user-bar-greeting"><%= link_to_login_with_IP 'Not logged in', :style => 'border: none;' %></div>
+ <div id="user-bar-action" ><%= link_to "Log in", login_path, { :title => "Log in" } %> /
+ <%= link_to "Sign up", signup_path, { :title => "Create an account" } %></div>
+<% end -%>
View
39 app/views/users/edit.html.erb
@@ -0,0 +1,39 @@
+<h2>Edit Profile - <%= @user.login %></h2>
+
+<% form_for( @user ) do |f| %>
+ <%= f.error_messages %>
+
+ <p>
+ <%= f.label :login %><br />
+ <%= @user.login %>
+ </p>
+
+ <p>
+ <%= f.label :name %><br />
+ <%= f.text_field :name %>
+ </p>
+
+ <p>
+ <%= f.label :email %><br />
+ <%= f.text_field :email %>
+ <p>
+
+ <p>
+ <%= f.label :psn, "PSN" %><br />
+ <%= f.text_field :psn %>
+ </p>
+
+ <p>
+ <%= f.label :time_zone %><br />
+ <%= f.time_zone_select :time_zone, @time_zones %>
+ </p>
+
+ <p>
+ <%= f.label :avatar_url %><br />
+ <%= f.text_field :avatar_url %>
+ </p>
+
+ <p>
+ <%= f.submit "Update" %>
+ </p>
+<% end %>
View
19 app/views/users/new.html.erb
@@ -0,0 +1,19 @@
+<h1>Sign up as a new user</h1>
+<% @user.password = @user.password_confirmation = nil %>
+
+<%= error_messages_for :user %>
+<% form_for :user, :url => users_path do |f| -%>
+<p><%= label_tag 'login' %><br/>
+<%= f.text_field :login %></p>
+
+<p><%= label_tag 'email' %><br/>
+<%= f.text_field :email %></p>
+
+<p><%= label_tag 'password' %><br/>
+<%= f.password_field :password %></p>
+
+<p><%= label_tag 'password_confirmation', 'Confirm Password' %><br/>
+<%= f.password_field :password_confirmation %></p>
+
+<p><%= submit_tag 'Sign up' %></p>
+<% end -%>
View
25 app/views/users/show.html.erb
@@ -0,0 +1,25 @@
+<h2><%= @user.login %></h2>
+
+<p>
+ <b>Login:</b> <%= h( @user.login ) %>
+</p>
+
+<p>
+ <b>Name:</b> <%= h( @user.name ) %>
+</p>
+
+<p>
+ <b>E-mail:</b> <%= h( @user.email ) %>
+</p>
+
+<p>
+ <b>PSN:</b> <%= h( @user.psn ) %>
+</p>
+
+<p>
+ <b>Avatar:</b> <%= if not h( @user.avatar_url ).blank? then image_tag( h(@user.avatar_url) ) end -%>
+</p>
+
+<p>
+ <b>TimeZone:</b> <%= h( @user.time_zone ) %>
+</p>
View
3  config/environment.rb
@@ -43,7 +43,8 @@
# 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'
+ #config.time_zone = 'UTC'
+ config.time_zone = 'Eastern Time (US & Canada)'
# The internationalization framework can be changed to have another default locale (standard is :en) or more load paths.
# All files from config/locales/*.rb,yml are added automatically.
View
38 config/initializers/site_keys.rb
@@ -0,0 +1,38 @@
+
+# A Site key gives additional protection against a dictionary attack if your
+# DB is ever compromised. With no site key, we store
+# DB_password = hash(user_password, DB_user_salt)
+# If your database were to be compromised you'd be vulnerable to a dictionary
+# attack on all your stupid users' passwords. With a site key, we store
+# DB_password = hash(user_password, DB_user_salt, Code_site_key)
+# That means an attacker needs access to both your site's code *and* its
+# database to mount an "offline dictionary attack.":http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/web-authentication.html
+#
+# It's probably of minor importance, but recommended by best practices: 'defense
+# in depth'. Needless to say, if you upload this to github or the youtubes or
+# otherwise place it in public view you'll kinda defeat the point. Your users'
+# passwords are still secure, and the world won't end, but defense_in_depth -= 1.
+#
+# Please note: if you change this, all the passwords will be invalidated, so DO
+# keep it someplace secure. Use the random value given or type in the lyrics to
+# your favorite Jay-Z song or something; any moderately long, unpredictable text.
+REST_AUTH_SITE_KEY = 'a6330769284b507d0e61f5a4e458395255a2ff0c'
+
+# Repeated applications of the hash make brute force (even with a compromised
+# database and site key) harder, and scale with Moore's law.
+#
+# bq. "To squeeze the most security out of a limited-entropy password or
+# passphrase, we can use two techniques [salting and stretching]... that are
+# so simple and obvious that they should be used in every password system.
+# There is really no excuse not to use them." http://tinyurl.com/37lb73
+# Practical Security (Ferguson & Scheier) p350
+#
+# A modest 10 foldings (the default here) adds 3ms. This makes brute forcing 10
+# times harder, while reducing an app that otherwise serves 100 reqs/s to 78 signin
+# reqs/s, an app that does 10reqs/s to 9.7 reqs/s
+#
+# More:
+# * http://www.owasp.org/index.php/Hashing_Java
+# * "An Illustrated Guide to Cryptographic Hashes":http://www.unixwiz.net/techtips/iguide-crypto-hashes.html
+
+REST_AUTH_DIGEST_STRETCHES = 10
View
8 config/routes.rb
@@ -1,4 +1,12 @@
ActionController::Routing::Routes.draw do |map|
+ map.logout '/logout', :controller => 'sessions', :action => 'destroy'
+ map.login '/login', :controller => 'sessions', :action => 'new'
+ map.register '/register', :controller => 'users', :action => 'create'
+ map.signup '/signup', :controller => 'users', :action => 'new'
+ map.resources :users
+
+ map.resource :session
+
# The priority is based upon order of creation: first created -> highest priority.
# Sample of regular route:
View
20 db/migrate/20081215014407_create_users.rb
@@ -0,0 +1,20 @@
+class CreateUsers < ActiveRecord::Migration
+ def self.up
+ create_table "users", :force => true do |t|
+ t.string :login, :limit => 40
+ t.string :name, :limit => 100, :default => '', :null => true
+ t.string :email, :limit => 100
+ t.string :crypted_password, :limit => 40
+ t.string :salt, :limit => 40
+ t.datetime :created_at
+ t.datetime :updated_at
+ t.string :remember_token, :limit => 40
+ t.string :remember_token_expires_at
+ end
+ add_index :users, :login, :unique => true
+ end
+
+ def self.down
+ drop_table "users"
+ end
+end
View
17 db/migrate/20081215050613_add_fields_to_users.rb
@@ -0,0 +1,17 @@
+class AddFieldsToUsers < ActiveRecord::Migration
+ def self.up
+ change_table :users do |t|
+ t.string :time_zone
+ t.string :psn
+ t.string :avatar_url
+ end
+ end
+
+ def self.down
+ change_table :users do |t|
+ t.remove :time_zone
+ t.remove :psn
+ t.remove :avatar_url
+ end
+ end
+end
View
189 lib/authenticated_system.rb
@@ -0,0 +1,189 @@
+module AuthenticatedSystem
+ protected
+ # Returns true or false if the user is logged in.
+ # Preloads @current_user with the user model if they're logged in.
+ def logged_in?
+ !!current_user
+ end
+
+ # Accesses the current user from the session.
+ # Future calls avoid the database because nil is not equal to false.
+ def current_user
+ @current_user ||= (login_from_session || login_from_basic_auth || login_from_cookie) unless @current_user == false
+ end
+
+ # Store the given user id in the session.
+ def current_user=(new_user)
+ session[:user_id] = new_user ? new_user.id : nil
+ @current_user = new_user || false
+ end
+
+ # Check if the user is authorized
+ #
+ # Override this method in your controllers if you want to restrict access
+ # to only a few actions or if you want to check if the user
+ # has the correct rights.
+ #
+ # Example:
+ #
+ # # only allow nonbobs
+ # def authorized?
+ # current_user.login != "bob"
+ # end
+ #
+ def authorized?(action = action_name, resource = nil)
+ logged_in?
+ end
+
+ # Filter method to enforce a login requirement.
+ #
+ # To require logins for all actions, use this in your controllers:
+ #
+ # before_filter :login_required
+ #
+ # To require logins for specific actions, use this in your controllers:
+ #
+ # before_filter :login_required, :only => [ :edit, :update ]
+ #
+ # To skip this in a subclassed controller:
+ #
+ # skip_before_filter :login_required
+ #
+ def login_required
+ authorized? || access_denied
+ end
+
+ # Redirect as appropriate when an access request fails.
+ #
+ # The default action is to redirect to the login screen.
+ #
+ # Override this method in your controllers if you want to have special
+ # behavior in case the user is not authorized
+ # to access the requested action. For example, a popup window might
+ # simply close itself.
+ def access_denied
+ respond_to do |format|
+ format.html do
+ store_location
+ redirect_to new_session_path
+ end
+ # format.any doesn't work in rails version < http://dev.rubyonrails.org/changeset/8987
+ # Add any other API formats here. (Some browsers, notably IE6, send Accept: */* and trigger
+ # the 'format.any' block incorrectly. See http://bit.ly/ie6_borken or http://bit.ly/ie6_borken2
+ # for a workaround.)
+ format.any(:json, :xml) do
+ request_http_basic_authentication 'Web Password'
+ end
+ end
+ end
+
+ # Store the URI of the current request in the session.
+ #
+ # We can return to this location by calling #redirect_back_or_default.
+ def store_location
+ session[:return_to] = request.request_uri
+ end
+
+ # Redirect to the URI stored by the most recent store_location call or
+ # to the passed default. Set an appropriately modified
+ # after_filter :store_location, :only => [:index, :new, :show, :edit]
+ # for any controller you want to be bounce-backable.
+ def redirect_back_or_default(default)
+ redirect_to(session[:return_to] || default)
+ session[:return_to] = nil
+ end
+
+ # Inclusion hook to make #current_user and #logged_in?
+ # available as ActionView helper methods.
+ def self.included(base)
+ base.send :helper_method, :current_user, :logged_in?, :authorized? if base.respond_to? :helper_method
+ end
+
+ #
+ # Login
+ #
+
+ # Called from #current_user. First attempt to login by the user id stored in the session.
+ def login_from_session
+ self.current_user = User.find_by_id(session[:user_id]) if session[:user_id]
+ end
+
+ # Called from #current_user. Now, attempt to login by basic authentication information.
+ def login_from_basic_auth
+ authenticate_with_http_basic do |login, password|
+ self.current_user = User.authenticate(login, password)
+ end
+ end
+
+ #
+ # Logout
+ #
+
+ # Called from #current_user. Finaly, attempt to login by an expiring token in the cookie.
+ # for the paranoid: we _should_ be storing user_token = hash(cookie_token, request IP)
+ def login_from_cookie
+ user = cookies[:auth_token] && User.find_by_remember_token(cookies[:auth_token])
+ if user && user.remember_token?
+ self.current_user = user
+ handle_remember_cookie! false # freshen cookie token (keeping date)
+ self.current_user
+ end
+ end
+
+ # This is ususally what you want; resetting the session willy-nilly wreaks
+ # havoc with forgery protection, and is only strictly necessary on login.
+ # However, **all session state variables should be unset here**.
+ def logout_keeping_session!
+ # Kill server-side auth cookie
+ @current_user.forget_me if @current_user.is_a? User
+ @current_user = false # not logged in, and don't do it for me
+ kill_remember_cookie! # Kill client-side auth cookie
+ session[:user_id] = nil # keeps the session but kill our variable
+ # explicitly kill any other session variables you set
+ end
+
+ # The session should only be reset at the tail end of a form POST --
+ # otherwise the request forgery protection fails. It's only really necessary
+ # when you cross quarantine (logged-out to logged-in).
+ def logout_killing_session!
+ logout_keeping_session!
+ reset_session
+ end
+
+ #
+ # Remember_me Tokens
+ #
+ # Cookies shouldn't be allowed to persist past their freshness date,
+ # and they should be changed at each login
+
+ # Cookies shouldn't be allowed to persist past their freshness date,
+ # and they should be changed at each login
+
+ def valid_remember_cookie?
+ return nil unless @current_user
+ (@current_user.remember_token?) &&
+ (cookies[:auth_token] == @current_user.remember_token)
+ end
+
+ # Refresh the cookie auth token if it exists, create it otherwise
+ def handle_remember_cookie!(new_cookie_flag)
+ return unless @current_user
+ case
+ when valid_remember_cookie? then @current_user.refresh_token # keeping same expiry date
+ when new_cookie_flag then @current_user.remember_me
+ else @current_user.forget_me
+ end
+ send_remember_cookie!
+ end
+
+ def kill_remember_cookie!
+ cookies.delete :auth_token
+ end
+
+ def send_remember_cookie!
+ cookies[:auth_token] = {
+ :value => @current_user.remember_token,
+ :expires => @current_user.remember_token_expires_at }
+ end
+
+end
View
11 lib/authenticated_test_helper.rb
@@ -0,0 +1,11 @@
+module AuthenticatedTestHelper
+ # Sets the current user in the session from the user fixtures.
+ def login_as(user)
+ @request.session[:user_id] = user ? users(user).id : nil
+ end
+
+ def authorize_as(user)
+ @request.env["HTTP_AUTHORIZATION"] = user ? ActionController::HttpAuthentication::Basic.encode_credentials(users(user).login, 'monkey') : nil
+ end
+
+end
View
31 test/fixtures/users.yml
@@ -0,0 +1,31 @@
+
+quentin:
+ id: 1
+ login: quentin
+ email: quentin@example.com
+ salt: 356a192b7913b04c54574d18c28d46e6395428ab # SHA1('0')
+ crypted_password: e687f06ce5edd525b5fa406ef5c8ac1e6f62d589 # 'monkey'
+ created_at: <%= 5.days.ago.to_s :db %>
+ remember_token_expires_at: <%= 1.days.from_now.to_s %>
+ remember_token: 77de68daecd823babbb58edb1c8e14d7106e83bb
+
+aaron:
+ id: 2
+ login: aaron
+ email: aaron@example.com
+ salt: da4b9237bacccdf19c0760cab7aec4a8359010b0 # SHA1('1')
+ crypted_password: e53d5bc30135b291be22fb659ed9fc4b8a0f01e1 # 'monkey'
+ created_at: <%= 1.days.ago.to_s :db %>
+ remember_token_expires_at:
+ remember_token:
+
+
+old_password_holder:
+ id: 3
+ login: old_password_holder
+ email: salty_dog@example.com
+ salt: 7e3041ebc2fc05a40c60028e2c4901a81035d3cd
+ crypted_password: 00742970dc9e6319f8019fd54864d3ea740f04b1 # test
+ created_at: <%= 1.days.ago.to_s :db %>
+
+
View
82 test/functional/sessions_controller_test.rb
@@ -0,0 +1,82 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'sessions_controller'
+
+# Re-raise errors caught by the controller.
+class SessionsController; def rescue_action(e) raise e end; end
+
+class SessionsControllerTest < ActionController::TestCase
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
+ # Then, you can remove it from this and the units test.
+ include AuthenticatedTestHelper
+
+ fixtures :users
+
+ def test_should_login_and_redirect
+ post :create, :login => 'quentin', :password => 'monkey'
+ assert session[:user_id]
+ assert_response :redirect
+ end
+
+ def test_should_fail_login_and_not_redirect
+ post :create, :login => 'quentin', :password => 'bad password'
+ assert_nil session[:user_id]
+ assert_response :success
+ end
+
+ def test_should_logout
+ login_as :quentin
+ get :destroy
+ assert_nil session[:user_id]
+ assert_response :redirect
+ end
+
+ def test_should_remember_me
+ @request.cookies["auth_token"] = nil
+ post :create, :login => 'quentin', :password => 'monkey', :remember_me => "1"
+ assert_not_nil @response.cookies["auth_token"]
+ end
+
+ def test_should_not_remember_me
+ @request.cookies["auth_token"] = nil
+ post :create, :login => 'quentin', :password => 'monkey', :remember_me => "0"
+ puts @response.cookies["auth_token"]
+ assert @response.cookies["auth_token"].blank?
+ end
+
+ def test_should_delete_token_on_logout
+ login_as :quentin
+ get :destroy
+ assert @response.cookies["auth_token"].blank?
+ end
+
+ def test_should_login_with_cookie
+ users(:quentin).remember_me
+ @request.cookies["auth_token"] = cookie_for(:quentin)
+ get :new
+ assert @controller.send(:logged_in?)
+ end
+
+ def test_should_fail_expired_cookie_login
+ users(:quentin).remember_me
+ users(:quentin).update_attribute :remember_token_expires_at, 5.minutes.ago
+ @request.cookies["auth_token"] = cookie_for(:quentin)
+ get :new
+ assert !@controller.send(:logged_in?)
+ end
+
+ def test_should_fail_cookie_login
+ users(:quentin).remember_me
+ @request.cookies["auth_token"] = auth_token('invalid_auth_token')
+ get :new
+ assert !@controller.send(:logged_in?)
+ end
+
+ protected
+ def auth_token(token)
+ CGI::Cookie.new('name' => 'auth_token', 'value' => token)
+ end
+
+ def cookie_for(user)
+ auth_token users(user).remember_token
+ end
+end
View
61 test/functional/users_controller_test.rb
@@ -0,0 +1,61 @@
+require File.dirname(__FILE__) + '/../test_helper'
+require 'users_controller'
+
+# Re-raise errors caught by the controller.
+class UsersController; def rescue_action(e) raise e end; end
+
+class UsersControllerTest < ActionController::TestCase
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead
+ # Then, you can remove it from this and the units test.
+ include AuthenticatedTestHelper
+
+ fixtures :users
+
+ def test_should_allow_signup
+ assert_difference 'User.count' do
+ create_user
+ assert_response :redirect
+ end
+ end
+
+ def test_should_require_login_on_signup
+ assert_no_difference 'User.count' do
+ create_user(:login => nil)
+ assert assigns(:user).errors.on(:login)
+ assert_response :success
+ end
+ end
+
+ def test_should_require_password_on_signup
+ assert_no_difference 'User.count' do
+ create_user(:password => nil)
+ assert assigns(:user).errors.on(:password)
+ assert_response :success
+ end
+ end
+
+ def test_should_require_password_confirmation_on_signup
+ assert_no_difference 'User.count' do
+ create_user(:password_confirmation => nil)
+ assert assigns(:user).errors.on(:password_confirmation)
+ assert_response :success
+ end
+ end
+
+ def test_should_require_email_on_signup
+ assert_no_difference 'User.count' do
+ create_user(:email => nil)
+ assert assigns(:user).errors.on(:email)
+ assert_response :success
+ end
+ end
+
+
+
+
+ protected
+ def create_user(options = {})
+ post :create, :user => { :login => 'quire', :email => 'quire@example.com',
+ :password => 'quire69', :password_confirmation => 'quire69' }.merge(options)
+ end
+end
View
103 test/unit/user_test.rb
@@ -0,0 +1,103 @@
+require File.dirname(__FILE__) + '/../test_helper'
+
+class UserTest < ActiveSupport::TestCase
+ # Be sure to include AuthenticatedTestHelper in test/test_helper.rb instead.
+ # Then, you can remove it from this and the functional test.
+ include AuthenticatedTestHelper
+ fixtures :users
+
+ def test_should_create_user
+ assert_difference 'User.count' do
+ user = create_user
+ assert !user.new_record?, "#{user.errors.full_messages.to_sentence}"
+ end
+ end
+
+ def test_should_require_login
+ assert_no_difference 'User.count' do
+ u = create_user(:login => nil)
+ assert u.errors.on(:login)
+ end
+ end
+
+ def test_should_require_password
+ assert_no_difference 'User.count' do
+ u = create_user(:password => nil)
+ assert u.errors.on(:password)
+ end
+ end
+
+ def test_should_require_password_confirmation
+ assert_no_difference 'User.count' do
+ u = create_user(:password_confirmation => nil)
+ assert u.errors.on(:password_confirmation)
+ end
+ end
+
+ def test_should_require_email
+ assert_no_difference 'User.count' do
+ u = create_user(:email => nil)
+ assert u.errors.on(:email)
+ end
+ end
+
+ def test_should_reset_password
+ users(:quentin).update_attributes(:password => 'new password', :password_confirmation => 'new password')
+ assert_equal users(:quentin), User.authenticate('quentin', 'new password')
+ end
+
+ def test_should_not_rehash_password
+ users(:quentin).update_attributes(:login => 'quentin2')
+ assert_equal users(:quentin), User.authenticate('quentin2', 'monkey')
+ end
+
+ def test_should_authenticate_user
+ assert_equal users(:quentin), User.authenticate('quentin', 'monkey')
+ end
+
+ def test_should_set_remember_token
+ users(:quentin).remember_me
+ assert_not_nil users(:quentin).remember_token
+ assert_not_nil users(:quentin).remember_token_expires_at
+ end
+
+ def test_should_unset_remember_token
+ users(:quentin).remember_me
+ assert_not_nil users(:quentin).remember_token
+ users(:quentin).forget_me
+ assert_nil users(:quentin).remember_token
+ end
+
+ def test_should_remember_me_for_one_week
+ before = 1.week.from_now.utc
+ users(:quentin).remember_me_for 1.week
+ after = 1.week.from_now.utc
+ assert_not_nil users(:quentin).remember_token
+ assert_not_nil users(:quentin).remember_token_expires_at
+ assert users(:quentin).remember_token_expires_at.between?(before, after)
+ end
+
+ def test_should_remember_me_until_one_week
+ time = 1.week.from_now.utc
+ users(:quentin).remember_me_until time
+ assert_not_nil users(:quentin).remember_token
+ assert_not_nil users(:quentin).remember_token_expires_at
+ assert_equal users(:quentin).remember_token_expires_at, time
+ end
+
+ def test_should_remember_me_default_two_weeks
+ before = 2.weeks.from_now.utc
+ users(:quentin).remember_me
+ after = 2.weeks.from_now.utc
+ assert_not_nil users(:quentin).remember_token
+ assert_not_nil users(:quentin).remember_token_expires_at
+ assert users(:quentin).remember_token_expires_at.between?(before, after)
+ end
+
+protected
+ def create_user(options = {})
+ record = User.new({ :login => 'quire', :email => 'quire@example.com', :password => 'quire69', :password_confirmation => 'quire69' }.merge(options))
+ record.save
+ record
+ end
+end
View
21 vendor/plugins/restful-authentication/.gitignore
@@ -0,0 +1,21 @@
+pkg
+Icon?
+.DS_Store
+TAGS
+REVISION
+*.tmproj
+.settings
+.project
+.tasks-cache
+.svn
+/log/*.log
+/tmp/**/*
+/config/database.yml
+actionmailer_config_DONOTVERSION.rb
+*DONOTVERSION*
+/vendor/src/**/*
+/db/*.sqlite*
+/public/ac/*
+/coverage
+/doc/app
+/doc/plugins
View
68 vendor/plugins/restful-authentication/CHANGELOG
@@ -0,0 +1,68 @@
+h1. Internal Changes to code
+
+As always, this is just a copy-and-pasted version of the CHANGELOG file in the source code tree.
+
+h2. Changes for the May, 2008 version of restful-authentication
+
+h3. Changes to user model
+
+* recently_activated? belongs only if stateful
+* Gave migration a 40-char limit on remember_token & an index on users by login
+* **Much** stricter login and email validation
+* put length constraints in migration too
+* password in 6, 40
+* salt and remember_token now much less predictability
+
+h3. Changes to session_controller
+
+* use uniform logout function
+* use uniform remember_cookie functions
+* avoid calling logged_in? which will auto-log-you-in (safe in the face of
+ logout! call, but idiot-proof)
+* Moved reset_session into only the "now logged in" branch
+** wherever it goes, it has to be in front of the current_user= call
+** See more in README-Tradeoffs.txt
+* made a place to take action on failed login attempt
+* recycle login and remember_me setting on failed login
+* nil'ed out the password field in 'new' view
+
+h3. Changes to users_controller
+
+* use uniform logout function
+* use uniform remember_cookie functions
+* Moved reset_session into only the "now logged in" branch
+** wherever it goes, it has to be in front of the current_user= call
+** See more in README-Tradeoffs.txt
+* made the implicit login only happen for non-activationed sites
+* On a failed signup, kick you back to the signin screen (but strip out the password & confirmation)
+* more descriptive error messages in activate()
+
+h3. users_helper
+
+* link_to_user, link_to_current_user, link_to_signin_with_IP
+* if_authorized(action, resource, &block) view function (with appropriate
+ warning)
+
+h3. authenticated_system
+
+* Made authorized? take optional arguments action=nil, resource=nil, *args
+ This makes its signature better match traditional approaches to access control
+ eg Reference Monitor in "Security Patterns":http://www.securitypatterns.org/patterns.html)
+* authorized? should be a helper too
+* added uniform logout! methods
+* format.any (as found in access_denied) doesn't work until
+ http://dev.rubyonrails.org/changeset/8987 lands.
+* cookies are now refreshed each time we cross the logged out/in barrier, as
+ "best":http://palisade.plynt.com/issues/2004Jul/safe-auth-practices/
+ "practice":http://www.owasp.org/index.php/Session_Management#Regeneration_of_Session_Tokens
+
+h3. Other
+
+* Used escapes <%= %> in email templates (among other reasons, so courtenay's
+ "'dumbass' test":http://tinyurl.com/684g9t doesn't complain)
+* Added site key to generator, users.yml.
+* Made site key generation idempotent in the most crude and hackish way
+* 100% coverage apart from the stateful code. (needed some access_control
+ checks, and the http_auth stuff)
+* Stories!
+
View
224 vendor/plugins/restful-authentication/README.textile
@@ -0,0 +1,224 @@
+h1. "Restful Authentication Generator":http://github.com/technoweenie/restful-authentication
+
+This widely-used plugin provides a foundation for securely managing user
+authentication:
+* Login / logout
+* Secure password handling
+* Account activation by validating email
+* Account approval / disabling by admin
+* Rudimentary hooks for authorization and access control.
+
+Several features were updated in May, 2008.
+* "Stable newer version":http://github.com/technoweenie/restful-authentication/tree/master
+* "'Classic' (backward-compatible) version":http://github.com/technoweenie/restful-authentication/tree/classic
+* "Experimental version":http://github.com/technoweenie/restful-authentication/tree/modular (Much more modular, needs testing & review)
+
+ !! important: if you upgrade your site, existing user account !!
+ !! passwords will stop working unless you use --old-passwords !!
+
+***************************************************************************
+
+h2. Issue Tracker
+
+Please submit any bugs or annoyances on the lighthouse tracker at
+* "http://rails_security.lighthouseapp.com/projects/15332-restful_authentication/overview":http://rails_security.lighthouseapp.com/projects/15332-restful_authentication/overview
+
+For anything simple enough, please github message both maintainers: Rick Olson
+("technoweenie":http://github.com/technoweenie) and Flip Kromer
+("mrflip":http://github.com/mrflip).
+
+***************************************************************************
+
+h2. Documentation
+
+This page has notes on
+* "Installation":#INSTALL
+* "New Features":#AWESOME
+* "After installing":#POST-INSTALL
+
+See the "wiki":http://github.com/technoweenie/restful-authentication/wikis/home
+(or the notes/ directory) if you want to learn more about:
+
+* "Extensions, Addons and Alternatives":addons such as HAML templates
+* "Security Design Patterns":security-patterns with "snazzy diagram":http://github.com/technoweenie/restful-authentication/tree/master/notes/SecurityFramework.png
+* [[Authentication]] -- Lets a visitor identify herself (and lay claim to her corresponding Roles and measure of Trust)
+* "Trust Metrics":Trustification -- Confidence we can rely on the outcomes of this visitor's actions.
+* [[Authorization]] and Policy -- Based on trust and identity, what actions may this visitor perform?
+* [[Access Control]] -- How the Authorization policy is actually enforced in your code (A: hopefully without turning it into a spaghetti of if thens)
+* [[Rails Plugins]] for Authentication, Trust, Authorization and Access Control
+* [[Tradeoffs]] -- for the paranoid or the curious, a rundown of tradeoffs made in the code
+* [[CHANGELOG]] -- Summary of changes to internals
+* [[TODO]] -- Ideas for how you can help
+
+These best version of the release notes are in the notes/ directory in the
+"source code":http://github.com/technoweenie/restful-authentication/tree/master
+-- look there for the latest version. The wiki versions are taken (manually)
+from there.
+
+***************************************************************************
+
+<a id="AWESOME"/> </a>
+h2. Exciting new features
+
+h3. Stories
+
+There are now RSpec stories that allow expressive, enjoyable tests for the
+authentication code. The flexible code for resource testing in stories was
+extended from "Ben Mabey's.":http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
+
+h3. Modularize to match security design patterns:
+
+* Authentication (currently: password, browser cookie token, HTTP basic)
+* Trust metric (email validation)
+* Authorization (stateful roles)
+* Leave a flexible framework that will play nicely with other access control / policy definition / trust metric plugins
+
+h3. Other
+
+* Added a few helper methods for linking to user pages
+* Uniform handling of logout, remember_token
+* Stricter email, login field validation
+* Minor security fixes -- see CHANGELOG
+
+***************************************************************************
+
+h2. Non-backwards compatible Changes
+
+Here are a few changes in the May 2008 release that increase "Defense in Depth"
+but may require changes to existing accounts
+
+* If you have an existing site, none of these changes are compelling enough to
+ warrant migrating your userbase.
+* If you are generating for a new site, all of these changes are low-impact.
+ You should apply them.
+
+h3. Passwords
+
+The new password encryption (using a site key salt and stretching) will break
+existing user accounts' passwords. We recommend you use the --old-passwords
+option or write a migration tool and submit it as a patch. See the
+[[Tradeoffs]] note for more information.
+
+h3. Validations
+
+By default, email and usernames are validated against a somewhat strict pattern; your users' values may be now illegal. Adjust to suit.
+
+***************************************************************************
+
+<a id="INSTALL"/> </a>
+h2. Installation
+
+This is a basic restful authentication generator for rails, taken from
+acts as authenticated. Currently it requires Rails 1.2.6 or above.
+
+**IMPORTANT FOR RAILS > 2.1 USERS** To avoid a @NameError@ exception ("lighthouse tracker ticket":http://rails_security.lighthouseapp.com/projects/15332-restful_authentication/tickets/2-not-a-valid-constant-name-errors#ticket-2-2), check out the code to have an _underscore_ and not _dash_ in its name:
+* either use <code>git clone git://github.com/technoweenie/restful-authentication.git restful_authentication</code>
+* or rename the plugin's directory to be <code>restful_authentication</code> after fetching it.
+
+To use the generator:
+
+ ./script/generate authenticated user sessions \
+ --include-activation \
+ --stateful \
+ --rspec \
+ --skip-migration \
+ --skip-routes \
+ --old-passwords
+
+* The first parameter specifies the model that gets created in signup (typically
+ a user or account model). A model with migration is created, as well as a
+ basic controller with the create method. You probably want to say "User" here.
+
+* The second parameter specifies the session controller name. This is the
+ controller that handles the actual login/logout function on the site.
+ (probably: "Session").
+
+* --include-activation: Generates the code for a ActionMailer and its respective
+ Activation Code through email.
+
+* --stateful: Builds in support for acts_as_state_machine and generates
+ activation code. (@--stateful@ implies @--include-activation@). Based on the
+ idea at [[http://www.vaporbase.com/postings/stateful_authentication]]. Passing
+ @--skip-migration@ will skip the user migration, and @--skip-routes@ will skip
+ resource generation -- both useful if you've already run this generator.
+ (Needs the "acts_as_state_machine plugin":http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/,
+ but new installs should probably run with @--aasm@ instead.)
+
+* --aasm: Works the same as stateful but uses the "updated aasm gem":http://github.com/rubyist/aasm/tree/master
+
+* --rspec: Generate RSpec tests and Stories in place of standard rails tests.
+ This requires the
+ "RSpec and Rspec-on-rails plugins":http://rspec.info/
+ (make sure you "./script/generate rspec" after installing RSpec.) The rspec
+ and story suite are much more thorough than the rails tests, and changes are
+ unlikely to be backported.
+
+* --old-passwords: Use the older password scheme (see [[#COMPATIBILITY]], above)
+
+* --skip-migration: Don't generate a migration file for this model
+
+* --skip-routes: Don't generate a resource line in @config/routes.rb@
+
+***************************************************************************
+<a id="POST-INSTALL"/> </a>
+h2. After installing
+
+The below assumes a Model named 'User' and a Controller named 'Session'; please
+alter to suit. There are additional security minutae in @notes/README-Tradeoffs@
+-- only the paranoid or the curious need bother, though.
+
+* Add these familiar login URLs to your @config/routes.rb@ if you like:
+
+ <pre><code>
+ map.signup '/signup', :controller => 'users', :action => 'new'
+ map.login '/login', :controller => 'session', :action => 'new'
+ map.logout '/logout', :controller => 'session', :action => 'destroy'
+ </code></pre>
+
+* With @--include-activation@, also add to your @config/routes.rb@:
+
+ <pre><code>
+ map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate', :activation_code => nil
+ </code></pre>
+
+ and add an observer to @config/environment.rb@:
+
+ <pre><code>
+ config.active_record.observers = :user_observer
+ </code></pre>
+
+ Pay attention, may be this is not an issue for everybody, but if you should
+ have problems, that the sent activation_code does match with that in the
+ database stored, reload your user object before sending its data through email
+ something like:
+
+ <pre><code>
+ class UserObserver < ActiveRecord::Observer
+ def after_create(user)
+ user.reload
+ UserMailer.deliver_signup_notification(user)
+ end
+ def after_save(user)
+ user.reload
+ UserMailer.deliver_activation(user) if user.recently_activated?
+ end
+ end
+ </code></pre>
+
+
+* With @--stateful@, add an observer to config/environment.rb:
+
+ <pre><code>
+ config.active_record.observers = :user_observer
+ </code></pre>
+
+ and modify the users resource line to read
+
+ map.resources :users, :member => { :suspend => :put,
+ :unsuspend => :put,
+ :purge => :delete }
+
+* If you use a public repository for your code (such as github, rubyforge,
+ gitorious, etc.) make sure to NOT post your site_keys.rb (add a line like
+ '/config/initializers/site_keys.rb' to your .gitignore or do the svn ignore
+ dance), but make sure you DO keep it backed up somewhere safe.
View
32 vendor/plugins/restful-authentication/Rakefile
@@ -0,0 +1,32 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+require 'rake/gempackagetask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the restful_authentication plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the restful_authentication plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'RestfulAuthentication'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
+
+gemspec = eval(File.read("#{File.dirname(__FILE__)}/restful-authentication.gemspec"))
+PKG_NAME = gemspec.name
+PKG_VERSION = gemspec.version
+
+Rake::GemPackageTask.new(gemspec) do |pkg|
+ pkg.need_zip = true
+ pkg.need_tar = true
+end
View
15 vendor/plugins/restful-authentication/TODO
@@ -0,0 +1,15 @@
+
+h3. Authentication security projects for a later date
+
+
+* Track 'failed logins this hour' and demand a captcha after say 5 failed logins
+ ("RECAPTCHA plugin.":http://agilewebdevelopment.com/plugins/recaptcha)
+ "De-proxy-ficate IP address": http://wiki.codemongers.com/NginxHttpRealIpModule
+
+* Make cookie spoofing a little harder: we set the user's cookie to
+ (remember_token), but store digest(remember_token, request_IP). A CSRF cookie
+ spoofer has to then at least also spoof the user's originating IP
+ (see "Secure Programs HOWTO":http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/web-authentication.html)
+
+* Log HTTP request on authentication / authorization failures
+ http://palisade.plynt.com/issues/2004Jul/safe-auth-practices
View
1  vendor/plugins/restful-authentication/generators/authenticated/USAGE
@@ -0,0 +1 @@
+./script/generate authenticated USERMODEL CONTROLLERNAME
View
478 vendor/plugins/restful-authentication/generators/authenticated/authenticated_generator.rb
@@ -0,0 +1,478 @@
+require File.expand_path(File.dirname(__FILE__) + "/lib/insert_routes.rb")
+require 'digest/sha1'
+class AuthenticatedGenerator < Rails::Generator::NamedBase
+ default_options :skip_migration => false,
+ :skip_routes => false,
+ :old_passwords => false,
+ :include_activation => false
+
+ attr_reader :controller_name,
+ :controller_class_path,
+ :controller_file_path,
+ :controller_class_nesting,
+ :controller_class_nesting_depth,
+ :controller_class_name,
+ :controller_singular_name,
+ :controller_plural_name,
+ :controller_routing_name, # new_session_path
+ :controller_routing_path, # /session/new
+ :controller_controller_name, # sessions
+ :controller_file_name
+ alias_method :controller_table_name, :controller_plural_name
+ attr_reader :model_controller_name,
+ :model_controller_class_path,
+ :model_controller_file_path,
+ :model_controller_class_nesting,
+ :model_controller_class_nesting_depth,
+ :model_controller_class_name,
+ :model_controller_singular_name,
+ :model_controller_plural_name,
+ :model_controller_routing_name, # new_user_path
+ :model_controller_routing_path, # /users/new
+ :model_controller_controller_name # users
+ alias_method :model_controller_file_name, :model_controller_singular_name
+ alias_method :model_controller_table_name, :model_controller_plural_name
+
+ def initialize(runtime_args, runtime_options = {})
+ super
+
+ @rspec = has_rspec?
+
+ @controller_name = (args.shift || 'sessions').pluralize
+ @model_controller_name = @name.pluralize
+
+ # sessions controller
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
+ @controller_class_name_without_nesting, @controller_file_name, @controller_plural_name = inflect_names(base_name)
+ @controller_singular_name = @controller_file_name.singularize
+ if @controller_class_nesting.empty?
+ @controller_class_name = @controller_class_name_without_nesting
+ else
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
+ end
+ @controller_routing_name = @controller_singular_name
+ @controller_routing_path = @controller_file_path.singularize
+ @controller_controller_name = @controller_plural_name
+
+ # model controller
+ base_name, @model_controller_class_path, @model_controller_file_path, @model_controller_class_nesting, @model_controller_class_nesting_depth = extract_modules(@model_controller_name)
+ @model_controller_class_name_without_nesting, @model_controller_singular_name, @model_controller_plural_name = inflect_names(base_name)
+
+ if @model_controller_class_nesting.empty?
+ @model_controller_class_name = @model_controller_class_name_without_nesting
+ else
+ @model_controller_class_name = "#{@model_controller_class_nesting}::#{@model_controller_class_name_without_nesting}"
+ end
+ @model_controller_routing_name = @table_name
+ @model_controller_routing_path = @model_controller_file_path
+ @model_controller_controller_name = @model_controller_plural_name
+
+ load_or_initialize_site_keys()
+
+ if options[:dump_generator_attribute_names]
+ dump_generator_attribute_names
+ end
+ end
+
+ def manifest
+ recorded_session = record do |m|
+ # Check for class naming collisions.
+ m.class_collisions controller_class_path, "#{controller_class_name}Controller", # Sessions Controller
+ "#{controller_class_name}Helper"
+ m.class_collisions model_controller_class_path, "#{model_controller_class_name}Controller", # Model Controller
+ "#{model_controller_class_name}Helper"
+ m.class_collisions class_path, "#{class_name}", "#{class_name}Mailer", "#{class_name}MailerTest", "#{class_name}Observer"
+ m.class_collisions [], 'AuthenticatedSystem', 'AuthenticatedTestHelper'
+
+ # Controller, helper, views, and test directories.
+ m.directory File.join('app/models', class_path)
+ m.directory File.join('app/controllers', controller_class_path)
+ m.directory File.join('app/controllers', model_controller_class_path)
+ m.directory File.join('app/helpers', controller_class_path)
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
+ m.directory File.join('app/views', class_path, "#{file_name}_mailer") if options[:include_activation]
+
+ m.directory File.join('app/controllers', model_controller_class_path)
+ m.directory File.join('app/helpers', model_controller_class_path)
+ m.directory File.join('app/views', model_controller_class_path, model_controller_file_name)
+ m.directory File.join('config/initializers')
+
+ if @rspec
+ m.directory File.join('spec/controllers', controller_class_path)
+ m.directory File.join('spec/controllers', model_controller_class_path)
+ m.directory File.join('spec/models', class_path)
+ m.directory File.join('spec/helpers', model_controller_class_path)
+ m.directory File.join('spec/fixtures', class_path)
+ m.directory File.join('stories', model_controller_file_path)
+ m.directory File.join('stories', 'steps')
+ else
+ m.directory File.join('test/functional', controller_class_path)
+ m.directory File.join('test/functional', model_controller_class_path)
+ m.directory File.join('test/unit', class_path)
+ m.directory File.join('test/fixtures', class_path)
+ end
+
+ m.template 'model.rb',
+ File.join('app/models',
+ class_path,
+ "#{file_name}.rb")
+
+ if options[:include_activation]
+ %w( mailer observer ).each do |model_type|
+ m.template "#{model_type}.rb", File.join('app/models',
+ class_path,
+ "#{file_name}_#{model_type}.rb")
+ end
+ end
+
+ m.template 'controller.rb',
+ File.join('app/controllers',
+ controller_class_path,
+ "#{controller_file_name}_controller.rb")
+
+ m.template 'model_controller.rb',
+ File.join('app/controllers',
+ model_controller_class_path,
+ "#{model_controller_file_name}_controller.rb")
+
+ m.template 'authenticated_system.rb',
+ File.join('lib', 'authenticated_system.rb')
+
+ m.template 'authenticated_test_helper.rb',
+ File.join('lib', 'authenticated_test_helper.rb')
+
+ m.template 'site_keys.rb', site_keys_file
+
+ if @rspec
+ # RSpec Specs
+ m.template 'spec/controllers/users_controller_spec.rb',
+ File.join('spec/controllers',
+ model_controller_class_path,
+ "#{model_controller_file_name}_controller_spec.rb")
+ m.template 'spec/controllers/sessions_controller_spec.rb',
+ File.join('spec/controllers',
+ controller_class_path,
+ "#{controller_file_name}_controller_spec.rb")
+ m.template 'spec/controllers/access_control_spec.rb',
+ File.join('spec/controllers',
+ controller_class_path,
+ "access_control_spec.rb")
+ m.template 'spec/controllers/authenticated_system_spec.rb',
+ File.join('spec/controllers',
+ controller_class_path,
+ "authenticated_system_spec.rb")
+ m.template 'spec/helpers/users_helper_spec.rb',
+ File.join('spec/helpers',
+ model_controller_class_path,
+ "#{table_name}_helper_spec.rb")
+ m.template 'spec/models/user_spec.rb',
+ File.join('spec/models',
+ class_path,
+ "#{file_name}_spec.rb")
+ m.template 'spec/fixtures/users.yml',
+ File.join('spec/fixtures',
+ class_path,
+ "#{table_name}.yml")
+
+ # RSpec Stories
+ m.template 'stories/steps/ra_navigation_steps.rb',
+ File.join('stories/steps/ra_navigation_steps.rb')
+ m.template 'stories/steps/ra_response_steps.rb',
+ File.join('stories/steps/ra_response_steps.rb')
+ m.template 'stories/steps/ra_resource_steps.rb',
+ File.join('stories/steps/ra_resource_steps.rb')
+ m.template 'stories/steps/user_steps.rb',
+ File.join('stories/steps/', "#{file_name}_steps.rb")
+ m.template 'stories/users/accounts.story',
+ File.join('stories', model_controller_file_path, 'accounts.story')
+ m.template 'stories/users/sessions.story',
+ File.join('stories', model_controller_file_path, 'sessions.story')
+ m.template 'stories/rest_auth_stories_helper.rb',
+ File.join('stories', 'rest_auth_stories_helper.rb')
+ m.template 'stories/rest_auth_stories.rb',
+ File.join('stories', 'rest_auth_stories.rb')
+
+ else
+ m.template 'test/functional_test.rb',
+ File.join('test/functional',
+ controller_class_path,
+ "#{controller_file_name}_controller_test.rb")
+ m.template 'test/model_functional_test.rb',
+ File.join('test/functional',
+ model_controller_class_path,
+ "#{model_controller_file_name}_controller_test.rb")
+ m.template 'test/unit_test.rb',
+ File.join('test/unit',
+ class_path,
+ "#{file_name}_test.rb")
+ if options[:include_activation]
+ m.template 'test/mailer_test.rb', File.join('test/unit', class_path, "#{file_name}_mailer_test.rb")
+ end
+ m.template 'spec/fixtures/users.yml',
+ File.join('test/fixtures',
+ class_path,
+ "#{table_name}.yml")
+ end
+
+ m.template 'helper.rb',
+ File.join('app/helpers',
+ controller_class_path,
+ "#{controller_file_name}_helper.rb")
+
+ m.template 'model_helper.rb',
+ File.join('app/helpers',
+ model_controller_class_path,
+ "#{model_controller_file_name}_helper.rb")
+
+
+ # Controller templates
+ m.template 'login.html.erb', File.join('app/views', controller_class_path, controller_file_name, "new.html.erb")
+ m.template 'signup.html.erb', File.join('app/views', model_controller_class_path, model_controller_file_name, "new.html.erb")
+ m.template '_model_partial.html.erb', File.join('app/views', model_controller_class_path, model_controller_file_name, "_#{file_name}_bar.html.erb")
+
+ if options[:include_activation]
+ # Mailer templates
+ %w( activation signup_notification ).each do |action|
+ m.template "#{action}.erb",
+ File.join('app/views', "#{file_name}_mailer", "#{action}.erb")
+ end
+ end
+
+ unless options[:skip_migration]
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => {
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
+ }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
+ end
+ unless options[:skip_routes]
+ # Note that this fails for nested classes -- you're on your own with setting up the routes.
+ m.route_resource controller_singular_name
+ m.route_resources model_controller_plural_name
+ m.route_name('signup', '/signup', {:controller => model_controller_plural_name, :action => 'new'})
+ m.route_name('register', '/register', {:controller => model_controller_plural_name, :action => 'create'})
+ m.route_name('login', '/login', {:controller => controller_controller_name, :action => 'new'})
+ m.route_name('logout', '/logout', {:controller => controller_controller_name, :action => 'destroy'})
+ end
+ end
+
+ #
+ # Post-install notes
+ #
+ action = File.basename($0) # grok the action from './script/generate' or whatever
+ case action
+ when "generate"
+ puts "Ready to generate."
+ puts ("-" * 70)
+ puts "Once finished, don't forget to:"
+ puts
+ if options[:include_activation]
+ puts "- Add an observer to config/environment.rb"
+ puts " config.active_record.observers = :#{file_name}_observer"
+ end
+ if options[:aasm]
+ puts "- Install the acts_as_state_machine gem:"
+ puts " sudo gem sources -a http://gems.github.com (If you haven't already)"
+ puts " sudo gem install rubyist-aasm"
+ elsif options[:stateful]
+ puts "- Install the acts_as_state_machine plugin:"
+ puts " svn export http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk vendor/plugins/acts_as_state_machine"
+ end
+ puts "- Add routes to these resources. In config/routes.rb, insert routes like:"
+ puts %( map.signup '/signup', :controller => '#{model_controller_file_name}', :action => 'new')
+ puts %( map.login '/login', :controller => '#{controller_file_name}', :action => 'new')
+ puts %( map.logout '/logout', :controller => '#{controller_file_name}', :action => 'destroy')
+ if options[:include_activation]
+ puts %( map.activate '/activate/:activation_code', :controller => '#{model_controller_file_name}', :action => 'activate', :activation_code => nil)
+ end
+ if options[:stateful]
+ puts " and modify the map.resources :#{model_controller_file_name} line to include these actions:"
+ puts " map.resources :#{model_controller_file_name}, :member => { :suspend => :put, :unsuspend => :put, :purge => :delete }"
+ end
+ puts
+ puts ("-" * 70)
+ puts
+ if $rest_auth_site_key_from_generator.blank?
+ puts "You've set a nil site key. This preserves existing users' passwords,"
+ puts "but allows dictionary attacks in the unlikely event your database is"
+ puts "compromised and your site code is not. See the README for more."
+ elsif $rest_auth_keys_are_new
+ puts "We've create a new site key in #{site_keys_file}. If you have existing"
+ puts "user accounts their passwords will no longer work (see README). As always,"
+ puts "keep this file safe but don't post it in public."
+ else
+ puts "We've reused the existing site key in #{site_keys_file}. As always,"
+ puts "keep this file safe but don't post it in public."
+ end
+ puts
+ puts ("-" * 70)
+ when "destroy"
+ puts
+ puts ("-" * 70)
+ puts
+ puts "Thanks for using restful_authentication"
+ puts
+ puts "Don't forget to comment out the observer line in environment.rb"
+ puts " (This was optional so it may not even be there)"
+ puts " # config.active_record.observers = :#{file_name}_observer"
+ puts
+ puts ("-" * 70)
+ puts
+ else
+ puts "Didn't understand the action '#{action}' -- you might have missed the 'after running me' instructions."
+ end
+
+ #
+ # Do the thing
+ #
+ recorded_session
+ end
+
+ def has_rspec?
+ spec_dir = File.join(RAILS_ROOT, 'spec')
+ options[:rspec] ||= (File.exist?(spec_dir) && File.directory?(spec_dir)) unless (options[:rspec] == false)
+ end
+
+ #
+ # !! These must match the corresponding routines in by_password.rb !!
+ #
+ def secure_digest(*args)
+ Digest::SHA1.hexdigest(args.flatten.join('--'))
+ end
+ def make_token
+ secure_digest(Time.now, (1..10).map{ rand.to_s })
+ end
+ def password_digest(password, salt)
+ digest = $rest_auth_site_key_from_generator
+ $rest_auth_digest_stretches_from_generator.times do
+ digest = secure_digest(digest, salt, password, $rest_auth_site_key_from_generator)
+ end
+ digest
+ end
+
+ #
+ # Try to be idempotent:
+ # pull in the existing site key if any,
+ # seed it with reasonable defaults otherwise
+ #
+ def load_or_initialize_site_keys
+ case
+ when defined? REST_AUTH_SITE_KEY
+ if (options[:old_passwords]) && ((! REST_AUTH_SITE_KEY.blank?) || (REST_AUTH_DIGEST_STRETCHES != 1))
+ raise "You have a site key, but --old-passwords will overwrite it. If this is really what you want, move the file #{site_keys_file} and re-run."
+ end
+ $rest_auth_site_key_from_generator = REST_AUTH_SITE_KEY
+ $rest_auth_digest_stretches_from_generator = REST_AUTH_DIGEST_STRETCHES
+ when options[:old_passwords]
+ $rest_auth_site_key_from_generator = nil
+ $rest_auth_digest_stretches_from_generator = 1
+ $rest_auth_keys_are_new = true
+ else
+ $rest_auth_site_key_from_generator = make_token
+ $rest_auth_digest_stretches_from_generator = 10
+ $rest_auth_keys_are_new = true
+ end
+ end
+ def site_keys_file
+ File.join("config", "initializers", "site_keys.rb")
+ end
+
+protected
+ # Override with your own usage banner.
+ def banner
+ "Usage: #{$0} authenticated ModelName [ControllerName]"
+ end
+
+ def add_options!(opt)
+ opt.separator ''
+ opt.separator 'Options:'
+ opt.on("--skip-migration",
+ "Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
+ opt.on("--include-activation",
+ "Generate signup 'activation code' confirmation via email") { |v| options[:include_activation] = true }
+ opt.on("--stateful",
+ "Use acts_as_state_machine. Assumes --include-activation") { |v| options[:include_activation] = options[:stateful] = true }
+ opt.on("--aasm",
+ "Use (gem) aasm. Assumes --include-activation") { |v| options[:include_activation] = options[:stateful] = options[:aasm] = true }
+ opt.on("--rspec",
+ "Force rspec mode (checks for RAILS_ROOT/spec by default)") { |v| options[:rspec] = true }
+ opt.on("--no-rspec",
+ "Force test (not RSpec mode") { |v| options[:rspec] = false }
+ opt.on("--skip-routes",
+ "Don't generate a resource line in config/routes.rb") { |v| options[:skip_routes] = v }
+ opt.on("--old-passwords",
+ "Use the older password encryption scheme (see README)") { |v| options[:old_passwords] = v }
+ opt.on("--dump-generator-attrs",
+ "(generator debug helper)") { |v| options[:dump_generator_attribute_names] = v }
+ end
+
+ def dump_generator_attribute_names
+ generator_attribute_names = [
+ :table_name,
+ :file_name,
+ :class_name,
+ :controller_name,
+ :controller_class_path,
+ :controller_file_path,
+ :controller_class_nesting,
+ :controller_class_nesting_depth,
+ :controller_class_name,
+ :controller_singular_name,
+ :controller_plural_name,
+ :controller_routing_name, # new_session_path
+ :controller_routing_path, # /session/new
+ :controller_controller_name, # sessions
+ :controller_file_name,
+ :controller_table_name, :controller_plural_name,
+ :model_controller_name,
+ :model_controller_class_path,
+ :model_controller_file_path,
+ :model_controller_class_nesting,
+ :model_controller_class_nesting_depth,
+ :model_controller_class_name,
+ :model_controller_singular_name,
+ :model_controller_plural_name,
+ :model_controller_routing_name, # new_user_path
+ :model_controller_routing_path, # /users/new
+ :model_controller_controller_name, # users
+ :model_controller_file_name, :model_controller_singular_name,
+ :model_controller_table_name, :model_controller_plural_name,
+ ]
+ generator_attribute_names.each do |attr|
+ puts "%-40s %s" % ["#{attr}:", self.send(attr)] # instance_variable_get("@#{attr.to_s}"
+ end
+
+ end
+end
+
+# ./script/generate authenticated FoonParent::Foon SporkParent::Spork -p --force --rspec --dump-generator-attrs
+# table_name: foon_parent_foons
+# file_name: foon
+# class_name: FoonParent::Foon
+# controller_name: SporkParent::Sporks
+# controller_class_path: spork_parent
+# controller_file_path: spork_parent/sporks
+# controller_class_nesting: SporkParent
+# controller_class_nesting_depth: 1
+# controller_class_name: SporkParent::Sporks
+# controller_singular_name: spork
+# controller_plural_name: sporks
+# controller_routing_name: spork
+# controller_routing_path: spork_parent/spork
+# controller_controller_name: sporks
+# controller_file_name: sporks
+# controller_table_name: sporks
+# controller_plural_name: sporks
+# model_controller_name: FoonParent::Foons
+# model_controller_class_path: foon_parent
+# model_controller_file_path: foon_parent/foons
+# model_controller_class_nesting: FoonParent
+# model_controller_class_nesting_depth: 1
+# model_controller_class_name: FoonParent::Foons
+# model_controller_singular_name: foons
+# model_controller_plural_name: foons
+# model_controller_routing_name: foon_parent_foons
+# model_controller_routing_path: foon_parent/foons
+# model_controller_controller_name: foons
+# model_controller_file_name: foons
+# model_controller_singular_name: foons
+# model_controller_table_name: foons
+# model_controller_plural_name: foons
View
54 vendor/plugins/restful-authentication/generators/authenticated/lib/insert_routes.rb
@@ -0,0 +1,54 @@
+Rails::Generator::Commands::Create.class_eval do
+ def route_resource(*resources)
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
+
+ logger.route "map.resource #{resource_list}"
+ unless options[:pretend]
+ gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
+ "#{match}\n map.resource #{resource_list}\n"
+ end
+ end
+ end
+
+ def route_name(name, path, route_options = {})
+ sentinel = 'ActionController::Routing::Routes.draw do |map|'
+
+ logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
+ unless options[:pretend]
+ gsub_file 'config/routes.rb', /(#{Regexp.escape(sentinel)})/mi do |match|
+ "#{match}\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
+ end
+ end
+ end
+end
+
+Rails::Generator::Commands::Destroy.class_eval do
+ def route_resource(*resources)
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
+ look_for = "\n map.resource #{resource_list}\n"
+ logger.route "map.resource #{resource_list}"
+ unless options[:pretend]
+ gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
+ end
+ end
+
+ def route_name(name, path, route_options = {})
+ look_for = "\n map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
+ logger.route "map.#{name} '#{path}', :controller => '#{route_options[:controller]}', :action => '#{route_options[:action]}'"
+ unless options[:pretend]
+ gsub_file 'config/routes.rb', /(#{look_for})/mi, ''
+ end
+ end
+end
+
+Rails::Generator::Commands::List.class_eval do
+ def route_resource(*resources)
+ resource_list = resources.map { |r| r.to_sym.inspect }.join(', ')
+ logger.route "map.resource #{resource_list}"
+ end
+
+ def route_name(name, path, options = {})
+ logger.route "map.#{name} '#{path}', :controller => '{options[:controller]}', :action => '#{options[:action]}'"
+ end
+end
View
8 ...plugins/restful-authentication/generators/authenticated/templates/_model_partial.html.erb
@@ -0,0 +1,8 @@
+<%% if logged_in? -%>
+ <div id="<%= file_name %>-bar-greeting">Logged in as <%%= link_to_current_<%= file_name %> :content_method => :login %></div>
+ <div id="<%= file_name %>-bar-action" >(<%%= link_to "Log out", logout_path, { :title => "Log out" } %>)</div>
+<%% else -%>
+ <div id="<%= file_name %>-bar-greeting"><%%= link_to_login_with_IP 'Not logged in', :style => 'border: none;' %></div>
+ <div id="<%= file_name %>-bar-action" ><%%= link_to "Log in", login_path, { :title => "Log in" } %> /
+ <%%= link_to "Sign up", signup_path, { :title => "Create an account" } %></div>
+<%% end -%>
View
3  vendor/plugins/restful-authentication/generators/authenticated/templates/activation.erb
@@ -0,0 +1,3 @@
+<%%=h @<%= file_name %>.login %>, your account has been activated. Welcome aboard!
+
+ <%%=h @url %>
View
189 ...plugins/restful-authentication/generators/authenticated/templates/authenticated_system.rb
@@ -0,0 +1,189 @@
+module AuthenticatedSystem
+ protected
+ # Returns true or false if the <%= file_name %> is logged in.
+ # Preloads @current_<%= file_name %> with the <%= file_name %> model if they're logged in.
+ def logged_in?
+ !!current_<%= file_name %>
+ end
+
+ # Accesses the current <%= file_name %> from the session.
+ # Future calls avoid the database because nil is not equal to false.
+ def current_<%= file_name %>
+ @current_<%= file_name %> ||= (login_from_session || login_from_basic_auth || login_from_cookie) unless @current_<%= file_name %> == false
+ end
+
+ # Store the given <%= file_name %> id in the session.
+ def current_<%= file_name %>=(new_<%= file_name %>)
+ session[:<%= file_name %>_id] = new_<%= file_name %> ? new_<%= file_name %>.id : nil
+ @current_<%= file_name %> = new_<%= file_name %> || false
+ end
+
+ # Check if the <%= file_name %> is authorized
+ #
+ # Override this method in your controllers if you want to restrict access
+ # to only a few actions or if you want to check if the <%= file_name %>
+ # has the correct rights.
+ #
+ # Example:
+ #
+ # # only allow nonbobs
+ # def authorized?
+ # current_<%= file_name %>.login != "bob"
+ # end
+ #
+ def authorized?(action = action_name, resource = nil)
+ logged_in?
+ end
+
+ # Filter method to enforce a login requirement.
+ #
+ # To require logins for all actions, use this in your controllers:
+ #
+ # before_filter :login_required
+ #
+ # To require logins for specific actions, use this in your controllers:
+ #
+ # before_filter :login_required, :only => [ :edit, :update ]
+ #
+ # To skip this in a subclassed controller:
+ #
+ # skip_before_filter :login_required
+ #
+ def login_required
+ authorized? || access_denied
+ end
+
+ # Redirect as appropriate when an access request fails.
+ #
+ # The default action is to redirect to the login screen.
+ #
+ # Override this method in your controllers if you want to have special
+ # behavior in case the <%= file_name %> is not authorized
+ # to access the requested action. For example, a popup window might
+ # simply close itself.
+ def access_denied
+ respond_to do |format|
+ format.html do
+ store_location
+ redirect_to new_<%= controller_routing_name %>_path
+ end
+ # format.any doesn't work in rails version < http://dev.rubyonrails.org/changeset/8987
+ # Add any other API formats here. (Some browsers, notably IE6, send Accept: */* and trigger
+ # the 'format.any' block incorrectly. See http://bit.ly/ie6_borken or http://bit.ly/ie6_borken2
+ # for a workaround.)
+ format.any(:json, :xml) do
+ request_http_basic_authentication 'Web Password'
+ end
+ end
+ end
+
+ # Store the URI of the current request in the session.
+ #
+ # We can return to this location by calling #redirect_back_or_default.
+ def store_location
+ session[:return_to] = request.request_uri
+ end
+
+ # Redirect to the URI stored by the most recent store_location call or
+ # to the passed default. Set an appropriately modified
+ # after_filter :store_location, :only => [:index, :new, :show, :edit]
+ # for any controller you want to be bounce-backable.
+ def redirect_back_or_default(default)
+ redirect_to(session[:return_to] || default)
+ session[:return_to] = nil
+ end
+
+ # Inclusion hook to make #current_<%= file_name %> and #logged_in?
+ # available as ActionView helper methods.
+ def self.included(base)
+ base.send :helper_method, :current_<%= file_name %>, :logged_in?, :authorized? if base.respond_to? :helper_method
+ end
+
+ #
+ # Login
+ #
+
+ # Called from #current_<%= file_name %>. First attempt to login by the <%= file_name %> id stored in the session.
+ def login_from_session
+ self.current_<%= file_name %> = <%= class_name %>.find_by_id(session[:<%= file_name %>_id]) if session[:<%= file_name %>_id]
+ end
+
+ # Called from #current_<%= file_name %>. Now, attempt to login by basic authentication information.
+ def login_from_basic_auth
+ authenticate_with_http_basic do |login, password|
+ self.current_<%= file_name %> = <%= class_name %>.authenticate(login, password)
+ end
+ end
+
+ #
+ # Logout
+ #
+
+ # Called from #current_<%= file_name %>. Finaly, attempt to login by an expiring token in the cookie.
+ # for the paranoid: we _should_ be storing <%= file_name %>_token = hash(cookie_token, request IP)
+ def login_from_cookie
+ <%= file_name %> = cookies[:auth_token] && <%= class_name %>.find_by_remember_token(cookies[:auth_token])
+ if <%= file_name %> && <%= file_name %>.remember_token?
+ self.current_<%= file_name %> = <%= file_name %>
+ handle_remember_cookie! false # freshen cookie token (keeping date)
+ self.current_<%= file_name %>
+ end
+ end
+
+ # This is ususally what you want; resetting the session willy-nilly wreaks
+ # havoc with forgery protection, and is only strictly necessary on login.
+ # However, **all session state variables should be unset here**.
+ def logout_keeping_session!
+ # Kill server-side auth cookie
+ @current_<%= file_name %>.forget_me if @current_<%= file_name %>.is_a? <%= class_name %>
+ @current_<%= file_name %> = false # not logged in, and don't do it for me
+ kill_remember_cookie! # Kill client-side auth cookie
+ session[:<%= file_name %>_id] = nil # keeps the session but kill our variable
+ # explicitly kill any other session variables you set
+ end
+
+ # The session should only be reset at the tail end of a form POST --
+ # otherwise the request forgery protection fails. It's only really necessary
+ # when you cross quarantine (logged-out to logged-in).
+ def logout_killing_session!
+ logout_keeping_session!
+ reset_session
+ end
+
+ #
+ # Remember_me Tokens
+ #
+ # Cookies shouldn't be allowed to persist past their freshness date,
+ # and they should be changed at each login
+
+ # Cookies shouldn't be allowed to persist past their freshness date,
+ # and they should be changed at each login
+
+ def valid_remember_cookie?
+ return nil unless @current_<%= file_name %>
+ (@current_<%= file_name %>.remember_token?) &&
+ (cookies[:auth_token] == @current_<%= file_name %>.remember_token)
+ end
+
+ # Refresh the cookie auth token if it exists, create it otherwise
+ def handle_remember_cookie!(new_cookie_flag)
+ return unless @current_<%= file_name %>
+ case
+ when valid_remember_cookie? then @current_<%= file_name %>.refresh_token # keeping same expiry date
+ when new_cookie_flag then @current_<%= file_name %>.remember_me
+ else @current_<%= file_name %>.forget_me
+ end
+ send_remember_cookie!
+ end
+
+ def kill_remember_cookie!
+ cookies.delete :auth_token
+ end
+
+ def send_remember_cookie!
+ cookies[:auth_token] = {
+ :value => @current_<%= file_name %>.remember_token,
+ :expires => @current_<%= file_name %>.remember_token_