Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

install restful_authentication as normal plugin

Signed-off-by: trebor8x <trobbe@online.de>
  • Loading branch information...
commit 36cebc05a390ffa4683ef9ffa8ed723a8c2cec50 1 parent 44a5c81
@grobie grobie authored committed
Showing with 4,976 additions and 0 deletions.
  1. +68 −0 vendor/plugins/restful_authentication/CHANGELOG
  2. +20 −0 vendor/plugins/restful_authentication/LICENSE
  3. +224 −0 vendor/plugins/restful_authentication/README.textile
  4. +32 −0 vendor/plugins/restful_authentication/Rakefile
  5. +15 −0 vendor/plugins/restful_authentication/TODO
  6. +1 −0  vendor/plugins/restful_authentication/generators/authenticated/USAGE
  7. +478 −0 vendor/plugins/restful_authentication/generators/authenticated/authenticated_generator.rb
  8. +54 −0 vendor/plugins/restful_authentication/generators/authenticated/lib/insert_routes.rb
  9. +8 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/_model_partial.html.erb
  10. +3 −0  vendor/plugins/restful_authentication/generators/authenticated/templates/activation.erb
  11. +189 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/authenticated_system.rb
  12. +22 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/authenticated_test_helper.rb
  13. +43 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/controller.rb
  14. +109 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/features/accounts.feature
  15. +134 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/features/sessions.feature
  16. +9 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/features/step_definitions/ra_env.rb
  17. +48 −0 .../restful_authentication/generators/authenticated/templates/features/step_definitions/ra_navigation_steps.rb
  18. +178 −0 ...ns/restful_authentication/generators/authenticated/templates/features/step_definitions/ra_resource_steps.rb
  19. +169 −0 ...ns/restful_authentication/generators/authenticated/templates/features/step_definitions/ra_response_steps.rb
  20. +81 −0 ...ul_authentication/generators/authenticated/templates/features/step_definitions/rest_auth_features_helper.rb
  21. +131 −0 ...r/plugins/restful_authentication/generators/authenticated/templates/features/step_definitions/user_steps.rb
  22. +2 −0  vendor/plugins/restful_authentication/generators/authenticated/templates/helper.rb
  23. +16 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/login.html.erb
  24. +25 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/mailer.rb
  25. +26 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/migration.rb
  26. +83 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/model.rb
  27. +85 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/model_controller.rb
  28. +93 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/model_helper.rb
  29. +158 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/model_helper_spec.rb
  30. +11 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/observer.rb
  31. +19 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/signup.html.erb
  32. +8 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/signup_notification.erb
  33. +38 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/site_keys.rb
  34. +90 −0 ...r/plugins/restful_authentication/generators/authenticated/templates/spec/controllers/access_control_spec.rb
  35. +102 −0 ...ins/restful_authentication/generators/authenticated/templates/spec/controllers/authenticated_system_spec.rb
  36. +139 −0 ...gins/restful_authentication/generators/authenticated/templates/spec/controllers/sessions_controller_spec.rb
  37. +198 −0 ...plugins/restful_authentication/generators/authenticated/templates/spec/controllers/users_controller_spec.rb
  38. +60 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/spec/fixtures/users.yml
  39. +141 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/spec/helpers/users_helper_spec.rb
  40. +290 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/spec/models/user_spec.rb
  41. +82 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/test/functional_test.rb
  42. +31 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/test/mailer_test.rb
  43. +93 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/test/model_functional_test.rb
  44. +164 −0 vendor/plugins/restful_authentication/generators/authenticated/templates/test/unit_test.rb
  45. +1 −0  vendor/plugins/restful_authentication/init.rb
  46. +40 −0 vendor/plugins/restful_authentication/lib/authentication.rb
  47. +82 −0 vendor/plugins/restful_authentication/lib/authentication/by_cookie_token.rb
  48. +64 −0 vendor/plugins/restful_authentication/lib/authentication/by_password.rb
  49. +14 −0 vendor/plugins/restful_authentication/lib/authorization.rb
  50. +63 −0 vendor/plugins/restful_authentication/lib/authorization/aasm_roles.rb
  51. +62 −0 vendor/plugins/restful_authentication/lib/authorization/stateful_roles.rb
  52. +14 −0 vendor/plugins/restful_authentication/lib/trustification.rb
  53. +20 −0 vendor/plugins/restful_authentication/lib/trustification/email_validation.rb
  54. +2 −0  vendor/plugins/restful_authentication/notes/AccessControl.txt
  55. +5 −0 vendor/plugins/restful_authentication/notes/Authentication.txt
  56. +154 −0 vendor/plugins/restful_authentication/notes/Authorization.txt
  57. +78 −0 vendor/plugins/restful_authentication/notes/RailsPlugins.txt
  58. BIN  vendor/plugins/restful_authentication/notes/SecurityFramework.graffle
  59. BIN  vendor/plugins/restful_authentication/notes/SecurityFramework.png
  60. +163 −0 vendor/plugins/restful_authentication/notes/SecurityPatterns.txt
  61. +126 −0 vendor/plugins/restful_authentication/notes/Tradeoffs.txt
  62. +49 −0 vendor/plugins/restful_authentication/notes/Trustification.txt
  63. +3 −0  vendor/plugins/restful_authentication/rails/init.rb
  64. +33 −0 vendor/plugins/restful_authentication/restful-authentication.gemspec
  65. +33 −0 vendor/plugins/restful_authentication/tasks/auth.rake
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
20 vendor/plugins/restful_authentication/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2009 rick olson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
View
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 "Cucumber":http://wiki.github.com/aslakhellesoy/cucumber/home features 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 'features'
+ m.directory File.join('features', 'step_definitions')
+ 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")
+
+ # Cucumber features
+ m.template 'features/step_definitions/ra_navigation_steps.rb',
+ File.join('features/step_definitions/ra_navigation_steps.rb')
+ m.template 'features/step_definitions/ra_response_steps.rb',
+ File.join('features/step_definitions/ra_response_steps.rb')
+ m.template 'features/step_definitions/ra_resource_steps.rb',
+ File.join('features/step_definitions/ra_resource_steps.rb')
+ m.template 'features/step_definitions/user_steps.rb',
+ File.join('features/step_definitions/', "#{file_name}_steps.rb")
+ m.template 'features/accounts.feature',
+ File.join('features', 'accounts.feature')
+ m.template 'features/sessions.feature',
+ File.join('features', 'sessions.feature')
+ m.template 'features/step_definitions/rest_auth_features_helper.rb',
+ File.join('features', 'step_definitions', 'rest_auth_features_helper.rb')
+ m.template 'features/step_definitions/ra_env.rb',
+ File.join('features', 'step_definitions', 'ra_env.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 vendor/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 vendor/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_expires_at }
+ end
+
+end
View
22 vendor/plugins/restful_authentication/generators/authenticated/templates/authenticated_test_helper.rb
@@ -0,0 +1,22 @@
+module AuthenticatedTestHelper
+ # Sets the current <%= file_name %> in the session from the <%= file_name %> fixtures.
+ def login_as(<%= file_name %>)
+ @request.session[:<%= file_name %>_id] = <%= file_name %> ? (<%= file_name %>.is_a?(<%= file_name.camelize %>) ? <%= file_name %>.id : <%= table_name %>(<%= file_name %>).id) : nil
+ end
+
+ def authorize_as(<%= file_name %>)
+ @request.env["HTTP_AUTHORIZATION"] = <%= file_name %> ? ActionController::HttpAuthentication::Basic.encode_credentials(<%= table_name %>(<%= file_name %>).login, 'monkey') : nil
+ end
+
+<% if options[:rspec] -%>
+ # rspec
+ def mock_<%= file_name %>
+ <%= file_name %> = mock_model(<%= class_name %>, :id => 1,
+ :login => 'user_name',
+ :name => 'U. Surname',
+ :to_xml => "<%= class_name %>-in-XML", :to_json => "<%= class_name %>-in-JSON",
+ :errors => [])
+ <%= file_name %>
+ end
+<% end -%>
+end
View
43 vendor/plugins/restful_authentication/generators/authenticated/templates/controller.rb
@@ -0,0 +1,43 @@
+# This controller handles the login/logout function of the site.
+class <%= controller_class_name %>Controller < ApplicationController
+ # Be sure to include AuthenticationSystem in Application Controller instead
+ include AuthenticatedSystem
+
+ # render new.rhtml
+ def new
+ end
+
+ def create
+ logout_keeping_session!
+ <%= file_name %> = <%= class_name %>.authenticate(params[:login], params[:password])
+ if <%= file_name %>
+ # 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_<%= file_name %> = <%= file_name %>
+ 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
109 vendor/plugins/restful_authentication/generators/authenticated/templates/features/accounts.feature
@@ -0,0 +1,109 @@
+Visitors should be in control of creating an account and of proving their
+essential humanity/accountability or whatever it is people think the
+id-validation does. We should be fairly skeptical about this process, as the
+identity+trust chain starts here.
+
+Story: Creating an account
+ As an anonymous user
+ I want to be able to create an account
+ So that I can be one of the cool kids
+
+ #
+ # Account Creation: Get entry form
+ #
+ Scenario: Anonymous user can start creating an account
+ Given an anonymous user
+ When she goes to /signup
+ Then she should be at the 'users/new' page
+ And the page should look AWESOME
+ And she should see a <form> containing a textfield: Login, textfield: Email, password: Password, password: 'Confirm Password', submit: 'Sign up'
+
+ #
+ # Account Creation
+ #
+ Scenario: Anonymous user can create an account
+ Given an anonymous user
+ And no user with login: 'Oona' exists
+ When she registers an account as the preloaded 'Oona'
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Thanks for signing up!'
+ And a user with login: 'oona' should exist
+ And the user should have login: 'oona', and email: 'unactivated@example.com'
+
+ And oona should be logged in
+
+
+ #
+ # Account Creation Failure: Account exists
+ #
+
+
+ Scenario: Anonymous user can not create an account replacing an activated account
+ Given an anonymous user
+ And an activated user named 'Reggie'
+ And we try hard to remember the user's updated_at, and created_at
+ When she registers an account with login: 'reggie', password: 'monkey', and email: 'reggie@example.com'
+ Then she should be at the 'users/new' page
+ And she should see an errorExplanation message 'Login has already been taken'
+ And she should not see an errorExplanation message 'Email has already been taken'
+ And a user with login: 'reggie' should exist
+ And the user should have email: 'registered@example.com'
+
+ And the user's created_at should stay the same under to_s
+ And the user's updated_at should stay the same under to_s
+ And she should not be logged in
+
+ #
+ # Account Creation Failure: Incomplete input
+ #
+ Scenario: Anonymous user can not create an account with incomplete or incorrect input
+ Given an anonymous user
+ And no user with login: 'Oona' exists
+ When she registers an account with login: '', password: 'monkey', password_confirmation: 'monkey' and email: 'unactivated@example.com'
+ Then she should be at the 'users/new' page
+ And she should see an errorExplanation message 'Login can't be blank'
+ And no user with login: 'oona' should exist
+
+ Scenario: Anonymous user can not create an account with no password
+ Given an anonymous user
+ And no user with login: 'Oona' exists
+ When she registers an account with login: 'oona', password: '', password_confirmation: 'monkey' and email: 'unactivated@example.com'
+ Then she should be at the 'users/new' page
+ And she should see an errorExplanation message 'Password can't be blank'
+ And no user with login: 'oona' should exist
+
+ Scenario: Anonymous user can not create an account with no password_confirmation
+ Given an anonymous user
+ And no user with login: 'Oona' exists
+ When she registers an account with login: 'oona', password: 'monkey', password_confirmation: '' and email: 'unactivated@example.com'
+ Then she should be at the 'users/new' page
+ And she should see an errorExplanation message 'Password confirmation can't be blank'
+ And no user with login: 'oona' should exist
+
+ Scenario: Anonymous user can not create an account with mismatched password & password_confirmation
+ Given an anonymous user
+ And no user with login: 'Oona' exists
+ When she registers an account with login: 'oona', password: 'monkey', password_confirmation: 'monkeY' and email: 'unactivated@example.com'
+ Then she should be at the 'users/new' page
+ And she should see an errorExplanation message 'Password doesn't match confirmation'
+ And no user with login: 'oona' should exist
+
+ Scenario: Anonymous user can not create an account with bad email
+ Given an anonymous user
+ And no user with login: 'Oona' exists
+ When she registers an account with login: 'oona', password: 'monkey', password_confirmation: 'monkey' and email: ''
+ Then she should be at the 'users/new' page
+ And she should see an errorExplanation message 'Email can't be blank'
+ And no user with login: 'oona' should exist
+ When she registers an account with login: 'oona', password: 'monkey', password_confirmation: 'monkey' and email: 'unactivated@example.com'
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Thanks for signing up!'
+ And a user with login: 'oona' should exist
+ And the user should have login: 'oona', and email: 'unactivated@example.com'
+
+ And oona should be logged in
+
+
+
View
134 vendor/plugins/restful_authentication/generators/authenticated/templates/features/sessions.feature
@@ -0,0 +1,134 @@
+Users want to know that nobody can masquerade as them. We want to extend trust
+only to visitors who present the appropriate credentials. Everyone wants this
+identity verification to be as secure and convenient as possible.
+
+Story: Logging in
+ As an anonymous user with an account
+ I want to log in to my account
+ So that I can be myself
+
+ #
+ # Log in: get form
+ #
+ Scenario: Anonymous user can get a login form.
+ Given an anonymous user
+ When she goes to /login
+ Then she should be at the new sessions page
+ And the page should look AWESOME
+ And she should see a <form> containing a textfield: Login, password: Password, and submit: 'Log in'
+
+ #
+ # Log in successfully, but don't remember me
+ #
+ Scenario: Anonymous user can log in
+ Given an anonymous user
+ And an activated user named 'reggie'
+ When she creates a singular sessions with login: 'reggie', password: 'monkey', remember me: ''
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Logged in successfully'
+ And reggie should be logged in
+ And she should not have an auth_token cookie
+
+ Scenario: Logged-in user who logs in should be the new one
+ Given an activated user named 'reggie'
+ And an activated user logged in as 'oona'
+ When she creates a singular sessions with login: 'reggie', password: 'monkey', remember me: ''
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Logged in successfully'
+ And reggie should be logged in
+ And she should not have an auth_token cookie
+
+ #
+ # Log in successfully, remember me
+ #
+ Scenario: Anonymous user can log in and be remembered
+ Given an anonymous user
+ And an activated user named 'reggie'
+ When she creates a singular sessions with login: 'reggie', password: 'monkey', remember me: '1'
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Logged in successfully'
+ And reggie should be logged in
+ And she should have an auth_token cookie
+ # assumes fixtures were run sometime
+ And her session store should have user_id: 4
+
+ #
+ # Log in unsuccessfully
+ #
+
+ Scenario: Logged-in user who fails logs in should be logged out
+ Given an activated user named 'oona'
+ When she creates a singular sessions with login: 'oona', password: '1234oona', remember me: '1'
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Logged in successfully'
+ And oona should be logged in
+ And she should have an auth_token cookie
+ When she creates a singular sessions with login: 'reggie', password: 'i_haxxor_joo'
+ Then she should be at the new sessions page
+ Then she should see an error message 'Couldn't log you in as 'reggie''
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
+
+ Scenario: Log-in with bogus info should fail until it doesn't
+ Given an activated user named 'reggie'
+ When she creates a singular sessions with login: 'reggie', password: 'i_haxxor_joo'
+ Then she should be at the new sessions page
+ Then she should see an error message 'Couldn't log you in as 'reggie''
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
+ When she creates a singular sessions with login: 'reggie', password: ''
+ Then she should be at the new sessions page
+ Then she should see an error message 'Couldn't log you in as 'reggie''
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
+ When she creates a singular sessions with login: '', password: 'monkey'
+ Then she should be at the new sessions page
+ Then she should see an error message 'Couldn't log you in as '''
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
+ When she creates a singular sessions with login: 'leonard_shelby', password: 'monkey'
+ Then she should be at the new sessions page
+ Then she should see an error message 'Couldn't log you in as 'leonard_shelby''
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
+ When she creates a singular sessions with login: 'reggie', password: 'monkey', remember me: '1'
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'Logged in successfully'
+ And reggie should be logged in
+ And she should have an auth_token cookie
+ # assumes fixtures were run sometime
+ And her session store should have user_id: 4
+
+
+ #
+ # Log out successfully (should always succeed)
+ #
+ Scenario: Anonymous (logged out) user can log out.
+ Given an anonymous user
+ When she goes to /logout
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'You have been logged out'
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
+
+ Scenario: Logged in user can log out.
+ Given an activated user logged in as 'reggie'
+ When she goes to /logout
+ Then she should be redirected to the home page
+ When she follows that redirect!
+ Then she should see a notice message 'You have been logged out'
+ And she should not be logged in
+ And she should not have an auth_token cookie
+ And her session store should not have user_id
View
9 ...or/plugins/restful_authentication/generators/authenticated/templates/features/step_definitions/ra_env.rb
@@ -0,0 +1,9 @@
+
+Before do
+ Fixtures.reset_cache
+ fixtures_folder = File.join(RAILS_ROOT, 'spec', 'fixtures')
+ Fixtures.create_fixtures(fixtures_folder, "users")
+end
+
+# Make visible for testing
+ApplicationController.send(:public, :logged_in?, :current_user, :authorized?)
View
48 ...stful_authentication/generators/authenticated/templates/features/step_definitions/ra_navigation_steps.rb
@@ -0,0 +1,48 @@
+#
+# Where to go
+#
+
+#
+# GET
+# Go to a given page.
+When "$actor goes to $path" do |actor, path|
+ case path
+ when 'the home page' then get '/'
+ else get path
+ end
+end
+
+# POST -- Ex:
+# When she creates a book with ISBN: '0967539854' and comment: 'I love this book' and rating: '4'
+# When she creates a singular session with login: 'reggie' and password: 'i_haxxor_joo'
+# Since I'm not smart enough to do it right, explicitly specify singular resources
+When /^(\w+) creates an? ([\w ]+) with ([\w: \',]+)$/ do |actor, resource, attributes|
+ attributes = attributes.to_hash_from_story
+ if resource =~ %r{singular ([\w/]+)}
+ resource = $1.downcase.singularize
+ post "/#{resource}", attributes
+ else
+ post "/#{resource.downcase.pluralize}", { resource.downcase.singularize => attributes }
+ end
+end
+
+# PUT
+When %r{$actor asks to update '$resource' with $attributes} do |_, resource, attributes|
+ attributes = attributes.to_hash_from_story
+ put "#{resource}", attributes
+ dump_response
+end
+
+# DELETE -- Slap together the POST-form-as-fake-HTTP-DELETE submission
+When %r{$actor asks to delete '$resource'} do |_, resource|
+ post "/#{resource.downcase.pluralize}", { :_method => :delete }
+ dump_response
+end
+
+
+# Redirect --
+# Rather than coding in get/get_via_redirect's and past/p_v_r's,
+# let's just demand that in the story itself.
+When "$actor follows that redirect!" do |actor|
+ follow_redirect!
+end
View
178 ...restful_authentication/generators/authenticated/templates/features/step_definitions/ra_resource_steps.rb
@@ -0,0 +1,178 @@
+# The flexible code for resource testing came out of code from Ben Mabey
+# http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
+
+#
+# Construct resources
+#
+
+#
+# Build a resource as described, store it as an @instance variable. Ex:
+# "Given a user with login: 'mojojojo'"
+# produces a User instance stored in @user with 'mojojojo' as its login
+# attribute.
+#
+Given "a $resource instance with $attributes" do |resource, attributes|
+ klass, instance, attributes = parse_resource_args resource, attributes
+ instance = klass.new(attributes)
+ instance.save!
+ find_resource(resource, attributes).should_not be_nil
+ keep_instance! resource, instance
+end
+
+#
+# Stuff attributes into a preexisting @resource
+# "And the user has thac0: 3"
+# takes the earlier-defined @user instance and sets its thac0 to '3'.
+#
+Given "the $resource has $attributes" do |resource, attributes|
+ klass, instance, attributes = parse_resource_args resource, attributes
+ attributes.each do |attr, val|
+ instance.send("#{attr}=", val)
+ end
+ instance.save!
+ find_resource(resource, attributes).should_not be_nil
+ keep_instance! resource, instance
+end
+
+#
+# Destroy all for this resource
+#
+Given "no $resource with $attr: '$val' exists" do |resource, attr, val|
+ klass, instance = parse_resource_args resource
+ klass.destroy_all(attr.to_sym => val)
+ instance = find_resource resource, attr.to_sym => val
+ instance.should be_nil
+ keep_instance! resource, instance
+end
+
+#
+# Then's for resources
+#
+
+# Resource like this DOES exist
+Then /^a (\w+) with ([\w: \']+) should exist$/ do |resource, attributes|
+ instance = find_resource resource, attributes
+ instance.should_not be_nil
+ keep_instance! resource, instance
+end
+# Resource like this DOES NOT exist
+Then /^no (\w+) with ([\w: \']+) should exist$/ do |resource, attributes|
+ instance = find_resource resource, attributes
+ instance.should be_nil
+end
+
+# Resource has attributes with given values
+Then "the $resource should have $attributes" do |resource, attributes|
+ klass, instance, attributes = parse_resource_args resource, attributes
+ attributes.each do |attr, val|
+ instance.send(attr).should == val
+ end
+end
+
+# Resource attributes should / should not be nil
+Then "the $resource's $attr should be nil" do |resource, attr|
+ klass, instance = parse_resource_args resource
+ instance.send(attr).should be_nil
+end
+Then "the $resource's $attr should not be nil" do |resource, attr|
+ klass, instance = parse_resource_args resource
+ instance.send(attr).should_not be_nil
+end
+
+#
+# Bank each of the @resource's listed attributes for later.
+#
+Given "we try hard to remember the $resource's $attributes" do |resource, attributes|
+ attributes = attributes.to_array_from_story
+ attributes.each do |attr|
+ memorize_resource_value resource, attr
+ end
+end
+#
+# Bank each of the @resource's listed attributes for later.
+#
+Given "we don't remember anything about the past" do
+ memorize_forget_all!
+end
+
+#
+# Compare @resource.attr to its earlier-memorized value.
+# Specify ' using method_name' (abs, to_s, &c) to coerce before comparing.
+# For important and mysterious reasons, timestamps want to_i or to_s.
+#
+Then /^the (\w+)\'s (\w+) should stay the same under (\w+)$/ do |resource, attr, func|
+ klass, instance = parse_resource_args resource
+ # Get the values
+ old_value = recall_resource_value(resource, attr)
+ new_value = instance.send(attr)
+ # Transform each value, maybe, using value.func
+ if func then new_value = new_value.send(func); old_value = old_value.send(func) end
+ # Compare
+ old_value.should eql(new_value)
+end
+
+#
+# Look for each for the given attributes in the page's text
+#
+Then "page should have the $resource's $attributes" do |resource, attributes|
+ actual_resource = instantize(resource)
+ attributes.split(/, and |, /).each do |attribute|
+ response.should have_text(/#{actual_resource.send(attribute.strip.gsub(" ","_"))}/)
+ end
+end
+
+#
+# Turn a resource name and a to_hash_from_story string like
+# "attr: 'value', attr2: 'value2', ... , and attrN: 'valueN'"
+# into
+# * klass -- the class matching that Resource
+# * instance -- the possibly-preexisting local instance value @resource
+# * attributes -- a hash matching the given attribute-list string
+#
+def parse_resource_args resource, attributes=nil
+ instance = instantize resource
+ klass = resource.classify.constantize
+ attributes = attributes.to_hash_from_story if attributes
+ [klass, instance, attributes]
+end
+
+#
+# Given a class name 'resource' and a hash of conditsion, find a model
+#
+def find_resource resource, conditions
+ klass, instance = parse_resource_args resource
+ conditions = conditions.to_hash_from_story unless (conditions.is_a? Hash)
+ klass.find(:first, :conditions => conditions)
+end
+
+#
+# Simple, brittle, useful: store the given resource's attribute
+# so we can compare it later.
+#
+def memorize_resource_value resource, attr
+ klass, instance = parse_resource_args resource
+ value = instance.send(attr)
+ @_memorized ||= {}
+ @_memorized[resource] ||= {}
+ @_memorized[resource][attr] = value
+ value
+end
+def recall_resource_value resource, attr
+ @_memorized[resource][attr]
+end
+def memorize_forget_all!
+ @_memorized = {}
+end
+
+#
+# Keep the object around in a local instance variable @resource.
+#
+# So, for instance,
+# klass, instance = parse_resource_args 'user'
+# instance = klass.new({login => 'me', password => 'monkey', ...})
+# keep_instance! resource, instance
+# keeps the just-constructed User model in the @user instance variable.
+#
+def keep_instance! resource, object
+ instance_variable_set("@#{resource}", object)
+end
View
169 ...restful_authentication/generators/authenticated/templates/features/step_definitions/ra_response_steps.rb
@@ -0,0 +1,169 @@
+#
+# What you should see when you get there
+#
+
+#
+# Destinations. Ex:
+# She should be at the new kids page
+# Tarkin should be at the destroy alderaan page
+# The visitor should be at the '/lolcats/download' form
+# The visitor should be redirected to '/hi/mom'
+#
+# It doesn't know anything about actual routes -- it just
+# feeds its output to render_template or redirect_to
+#
+Then "$actor should be at $path" do |_, path|
+ response.should render_template(grok_path(path))
+end
+
+Then "$actor should be redirected to $path" do |_, path|
+ response.should redirect_to(grok_path(path))
+end
+
+Then "the page should look AWESOME" do
+ response.should have_tag('head>title')
+ response.should have_tag('h1')
+ # response.should be_valid_xhtml
+end
+
+#
+# Tags
+#
+
+Then "the page should contain '$text'" do |_, text|
+ response.should have_text(/#{text}/)
+end
+
+# please note: this enforces the use of a <label> field
+Then "$actor should see a <$container> containing a $attributes" do |_, container, attributes|
+ attributes = attributes.to_hash_from_story
+ response.should have_tag(container) do
+ attributes.each do |tag, label|
+ case tag
+ when "textfield" then with_tag "input[type='text']"; with_tag("label", label)
+ when "password" then with_tag "input[type='password']"; with_tag("label", label)
+ when "submit" then with_tag "input[type='submit'][value='#{label}']"
+ else with_tag tag, label
+ end
+ end
+ end
+end
+
+#
+# Session, cookie variables
+#
+Then "$actor $token cookie should include $attrlist" do |_, token, attrlist|
+ attrlist = attrlist.to_array_from_story
+ cookies.include?(token).should be_true
+ attrlist.each do |val|
+ cookies[token].include?(val).should be_true
+ end
+end
+
+Then "$actor $token cookie should exist but not include $attrlist" do |_, token, attrlist|
+ attrlist = attrlist.to_array_from_story
+ cookies.include?(token).should be_true
+ puts [cookies, attrlist, token].to_yaml
+ attrlist.each do |val|
+ cookies[token].include?(val).should_not be_true
+ end
+end
+
+Then "$actor should have $an $token cookie" do |_, _, token|
+ cookies[token].should_not be_blank
+end
+Then "$actor should not have $an $token cookie" do |_, _, token|
+ cookies[token].should be_blank
+end
+
+Given "$actor has $an cookie jar with $attributes" do |_, _, attributes|
+ attributes = attributes.to_hash_from_story
+ attributes.each do |attr, val|
+ cookies[attr] = val
+ end
+end
+Given "$actor session store has no $attrlist" do |_, attrlist|
+ attrlist = attrlist.to_array_from_story
+ attrlist.each do |attr|
+ # Note that the comparison passes through 'to_s'
+ session[attr.to_sym] = nil
+ end
+end
+
+Then "$actor session store should have $attributes" do |_, attributes|
+ attributes = attributes.to_hash_from_story
+ attributes.each do |attr, val|
+ # Note that the comparison passes through 'to_s'
+ session[attr.to_sym].to_s.should eql(val)
+ end
+end
+
+Then "$actor session store should not have $attrlist" do |_, attrlist|
+ attrlist = attrlist.to_array_from_story
+ attrlist.each do |attr|
+ session[attr.to_sym].blank?.should be_true
+ end
+end
+
+#
+# Flash messages
+#
+
+Then /^she should +see an? (\w+) message '([\w !\']+)'$/ do |notice, message|
+ response.should have_flash(notice, %r{#{message}})
+end
+
+Then "$actor should not see $an $notice message '$message'" do |_, _, notice, message|
+ response.should_not have_flash(notice, %r{#{message}})
+end
+
+Then "$actor should see no messages" do |_|
+ ['error', 'warning', 'notice'].each do |notice|
+ response.should_not have_flash(notice)
+ end
+end
+
+RE_POLITENESS = /(?:please|sorry|thank(?:s| you))/i
+Then %r{we should be polite about it} do
+ response.should have_tag("div.error,div.notice", RE_POLITENESS)
+end
+Then %r{we should not even be polite about it} do
+ response.should_not have_tag("div.error,div.notice", RE_POLITENESS)
+end
+
+#
+# Resource's attributes
+#
+# "Then page should have the $resource's $attributes" is in resource_steps
+
+# helpful debug step
+Then "we dump the response" do
+ dump_response
+end
+
+
+def have_flash notice, *args
+ have_tag("div.#{notice}", *args)
+end
+
+RE_PRETTY_RESOURCE = /the (index|show|new|create|edit|update|destroy) (\w+) (page|form)/i
+RE_THE_FOO_PAGE = /the '?([^']*)'? (page|form)/i
+RE_QUOTED_PATH = /^'([^']*)'$/i
+def grok_path path
+ path.gsub(/\s+again$/,'') # strip trailing ' again'
+ case
+ when path == 'the home page' then dest = '/'
+ when path =~ RE_PRETTY_RESOURCE then dest = template_for $1, $2
+ when path =~ RE_THE_FOO_PAGE then dest = $1
+ when path =~ RE_QUOTED_PATH then dest = $1
+ else dest = path
+ end
+ dest
+end
+
+# turns 'new', 'road bikes' into 'road_bikes/new'
+# note that it's "action resource"
+def template_for(action, resource)
+ "#{resource.gsub(" ","_")}/#{action}"
+end
+
View
81 ...authentication/generators/authenticated/templates/features/step_definitions/rest_auth_features_helper.rb
@@ -0,0 +1,81 @@
+# If you have a global stories helper, move this line there:
+include AuthenticatedTestHelper
+
+# Most of the below came out of code from Ben Mabey
+# http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
+
+# These allow exceptions to come through as opposed to being caught and having non-helpful responses returned.
+ActionController::Base.class_eval do
+ def perform_action
+ perform_action_without_rescue
+ end
+end
+Dispatcher.class_eval do
+ def self.failsafe_response(output, status, exception = nil)
+ raise exception
+ end
+end
+
+#
+# Sugar for turning a story's attribute list into list, array, etc.
+#
+module ToFooFromStory
+ def ToFooFromStory.fix_key key
+ key.downcase.gsub(/\s+/, '_')
+ end
+ def ToFooFromStory.fix_value value
+ return '' if !value
+ value.strip!
+ case
+ when value =~ /^'(.*)'$/ then value = $1
+ when value =~ /^"(.*)"$/ then value = $1
+ when value == 'nil!' then value = nil
+ when value == 'non-nil!' then value = be_nil
+ when value =~ /^#\{(.*)\}$/ then value = eval($1)
+ end
+ value
+ end
+ # Converts a key: value list found in the steps into a hash.
+ # Example:
+ # ISBN: '0967539854' and comment: 'I love this book' and Quality rating: '4'
+ # # => {"quality_rating"=>"4", "isbn"=>"0967539854", "comment"=>"I love this book"}
+ def to_hash_from_story
+ hsh = self.split(/,? and |, /).inject({}) do |hash_so_far, key_value|
+ key, value = key_value.split(":")
+ if !value then warn "Couldn't understand story '#{self}': only understood up to the part '#{hash_so_far.to_yaml}'" end
+ hash_so_far.merge(ToFooFromStory::fix_key(key) => ToFooFromStory::fix_value(value))
+ end
+ end
+ # Coverts an attribute list found in the steps into an array
+ # Example:
+ # login, email, updated_at, and gravatar
+ # # => ['login', 'email', 'updated_at', 'gravatar']
+ def to_array_from_story
+ self.split(/,? and |, /).map do |value|
+ ToFooFromStory::fix_value(value)
+ end
+ end
+end
+class String
+ include ToFooFromStory
+end
+
+def instantize(string)
+ instance_variable_get("@#{string}")
+end
+
+#
+# Spew response onto screen -- painful but scrolling >> debugger
+#
+def dump_response
+ # note that @request and @template won't to_yaml and that @session includes @cgi
+ response_methods = response.instance_variables - ['@request', '@template', '@cgi']
+ request_methods = response.request.instance_variables - ['@session_options_with_string_keys', '@cgi', '@session']
+ response_methods.map!{|attr| attr.gsub(/^@/,'')}.sort!
+ request_methods.map!{ |attr| attr.gsub(/^@/,'')}.sort!
+ puts '', '*' * 75,
+ response.instance_values.slice(*response_methods).to_yaml,
+ "*" * 75, '',
+ response.request.instance_values.slice(*request_methods).to_yaml,
+ "*" * 75, ''
+end
View
131 ...lugins/restful_authentication/generators/authenticated/templates/features/step_definitions/user_steps.rb
@@ -0,0 +1,131 @@
+RE_User = %r{(?:(?:the )? *(\w+) *)}
+RE_User_TYPE = %r{(?: *(\w+)? *)}
+
+#
+# Setting
+#
+
+Given "an anonymous user" do
+ log_out!
+end
+
+Given "$an $user_type user with $attributes" do |_, user_type, attributes|
+ create_user! user_type, attributes.to_hash_from_story
+end
+
+Given "$an $user_type user named '$login'" do |_, user_type, login|
+ create_user! user_type, named_user(login)
+end
+
+Given "$an $user_type user logged in as '$login'" do |_, user_type, login|
+ create_user! user_type, named_user(login)
+ log_in_user!
+end
+
+Given "$actor is logged in" do |_, login|
+ log_in_user! @user_params || named_user(login)
+end
+
+Given "there is no $user_type user named '$login'" do |_, login|
+ @user = User.find_by_login(login)
+ @user.destroy! if @user
+ @user.should be_nil
+end
+
+#
+# Actions
+#
+When "$actor logs out" do
+ log_out
+end
+
+When "$actor registers an account as the preloaded '$login'" do |_, login|
+ user = named_user(login)
+ user['password_confirmation'] = user['password']
+ create_user user
+end
+
+When "$actor registers an account with $attributes" do |_, attributes|
+ create_user attributes.to_hash_from_story
+end
+
+
+When "$actor logs in with $attributes" do |_, attributes|
+ log_in_user attributes.to_hash_from_story
+end
+
+#
+# Result
+#
+Then "$actor should be invited to sign in" do |_|
+ response.should render_template('/sessions/new')
+end
+
+Then "$actor should not be logged in" do |_|
+ controller.logged_in?.should_not be_true
+end
+
+Then "$login should be logged in" do |login|
+ controller.logged_in?.should be_true
+ controller.current_user.should === @user
+ controller.current_user.login.should == login
+end
+
+def named_user login
+ user_params = {
+ 'admin' => {'id' => 1, 'login' => 'addie', 'password' => '1234addie', 'email' => 'admin@example.com', },
+ 'oona' => { 'login' => 'oona', 'password' => '1234oona', 'email' => 'unactivated@example.com'},
+ 'reggie' => { 'login' => 'reggie', 'password' => 'monkey', 'email' => 'registered@example.com' },
+ }
+ user_params[login.downcase]
+end
+
+#
+# User account actions.
+#
+# The ! methods are 'just get the job done'. It's true, they do some testing of
+# their own -- thus un-DRY'ing tests that do and should live in the user account
+# stories -- but the repetition is ultimately important so that a faulty test setup
+# fails early.
+#
+
+def log_out
+ get '/sessions/destroy'
+end
+
+def log_out!
+ log_out
+ response.should redirect_to('/')
+ follow_redirect!
+end
+
+def create_user(user_params={})
+ @user_params ||= user_params
+ post "/users", :user => user_params
+ @user = User.find_by_login(user_params['login'])
+end
+
+def create_user!(user_type, user_params)
+ user_params['password_confirmation'] ||= user_params['password'] ||= user_params['password']
+ create_user user_params
+ response.should redirect_to('/')
+ follow_redirect!
+
+end
+
+
+
+def log_in_user user_params=nil
+ @user_params ||= user_params
+ user_params ||= @user_params
+ post "/session", user_params
+ @user = User.find_by_login(user_params['login'])
+ controller.current_user
+end
+
+def log_in_user! *args
+ log_in_user *args
+ response.should redirect_to('/')
+ follow_redirect!
+ response.should have_flash("notice", /Logged in successfully/)
+end
View
2  vendor/plugins/restful_authentication/generators/authenticated/templates/helper.rb
@@ -0,0 +1,2 @@
+module <%= controller_class_name %>Helper
+end
View
16 vendor/plugins/restful_authentication/generators/authenticated/templates/login.html.erb
@@ -0,0 +1,16 @@
+<h1>Log In</h1>
+
+<%% form_tag <%= controller_routing_name %>_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
25 vendor/plugins/restful_authentication/generators/authenticated/templates/mailer.rb
@@ -0,0 +1,25 @@
+class <%= class_name %>Mailer < ActionMailer::Base
+ def signup_notification(<%= file_name %>)
+ setup_email(<%= file_name %>)
+ @subject += 'Please activate your new account'
+ <% if options[:include_activation] %>
+ @body[:url] = "http://YOURSITE/activate/#{<%= file_name %>.activation_code}"
+ <% else %>
+ @body[:url] = "http://YOURSITE/login/" <% end %>
+ end
+
+ def activation(<%= file_name %>)
+ setup_email(<%= file_name %>)
+ @subject += 'Your account has been activated!'
+ @body[:url] = "http://YOURSITE/"
+ end
+
+ protected
+ def setup_email(<%= file_name %>)
+ @recipients = "#{<%= file_name %>.email}"
+ @from = "ADMINEMAIL"
+ @subject = "[YOURSITE] "
+ @sent_on = Time.now
+ @body[:<%= file_name %>] = <%= file_name %>
+ end
+end
View
26 vendor/plugins/restful_authentication/generators/authenticated/templates/migration.rb
@@ -0,0 +1,26 @@
+class <%= migration_name %> < ActiveRecord::Migration
+ def self.up
+ create_table "<%= table_name %>", :force => true do |t|
+ t.column :login, :string, :limit => 40
+ t.column :name, :string, :limit => 100, :default => '', :null => true
+ t.column :email, :string, :limit => 100
+ t.column :crypted_password, :string, :limit => 40
+ t.column :salt, :string, :limit => 40
+ t.column :created_at, :datetime
+ t.column :updated_at, :datetime
+ t.column :remember_token, :string, :limit => 40
+ t.column :remember_token_expires_at, :datetime
+<% if options[:include_activation] -%>
+ t.column :activation_code, :string, :limit => 40
+ t.column :activated_at, :datetime<% end %>
+<% if options[:stateful] -%>
+ t.column :state, :string, :null => :no, :default => 'passive'
+ t.column :deleted_at, :datetime<% end %>
+ end
+ add_index :<%= table_name %>, :login, :unique => true
+ end
+
+ def self.down
+ drop_table "<%= table_name %>"
+ end
+end
View
83 vendor/plugins/restful_authentication/generators/authenticated/templates/model.rb
@@ -0,0 +1,83 @@
+require 'digest/sha1'
+
+class <%= class_name %> < ActiveRecord::Base
+ include Authentication
+ include Authentication::ByPassword
+ include Authentication::ByCookieToken
+<% if options[:aasm] -%>
+ include Authorization::AasmRoles
+<% elsif options[:stateful] -%>
+ include Authorization::StatefulRoles<% end %>
+ 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
+
+ <% if options[:include_activation] && !options[:stateful] %>before_create :make_activation_code <% end %>
+
+ # 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
+
+<% if options[:include_activation] && !options[:stateful] %>
+ # Activates the user in the database.
+ def activate!
+ @activated = true
+ self.activated_at = Time.now.utc
+ self.activation_code = nil
+ save(false)
+ end
+
+ # Returns true if the user has just been activated.
+ def recently_activated?
+ @activated
+ end
+
+ def active?
+ # the existence of an activation code means they have not activated yet
+ activation_code.nil?
+ end<% end %>
+
+ # 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 = <% if options[:stateful] %>find_in_state :first, :active, :conditions => {:login => login.downcase}<%
+ elsif options[:include_activation] %>find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login]<%
+ else %>find_by_login(login.downcase)<% end %> # 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
+
+<% if options[:include_activation] -%>
+ def make_activation_code
+ <% if options[:stateful] -%>
+ self.deleted_at = nil
+ <% end -%>
+ self.activation_code = self.class.make_token
+ end
+<% end %>
+
+end
View
85 vendor/plugins/restful_authentication/generators/authenticated/templates/model_controller.rb
@@ -0,0 +1,85 @@
+class <%= model_controller_class_name %>Controller < ApplicationController
+ # Be sure to include AuthenticationSystem in Application Controller instead
+ include AuthenticatedSystem
+ <% if options[:stateful] %>
+ # Protect these actions behind an admin login
+ # before_filter :admin_required, :only => [:suspend, :unsuspend, :destroy, :purge]
+ before_filter :find_<%= file_name %>, :only => [:suspend, :unsuspend, :destroy, :purge]
+ <% end %>
+
+ # render new.rhtml
+ def new
+ @<%= file_name %> = <%= class_name %>.new
+ end
+
+ def create
+ logout_keeping_session!
+ @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>])
+<% if options[:stateful] -%>
+ @<%= file_name %>.register! if @<%= file_name %> && @<%= file_name %>.valid?
+ success = @<%= file_name %> && @<%= file_name %>.valid?
+<% else -%>
+ success = @<%= file_name %> && @<%= file_name %>.save
+<% end -%>
+ if success && @<%= file_name %>.errors.empty?
+ <% if !options[:include_activation] -%>
+ # 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_<%= file_name %> = @<%= file_name %> # !! now logged in
+ <% end -%>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
+<% if options[:include_activation] %>
+ def activate
+ logout_keeping_session!
+ <%= file_name %> = <%= class_name %>.find_by_activation_code(params[:activation_code]) unless params[:activation_code].blank?
+ case
+ when (!params[:activation_code].blank?) && <%= file_name %> && !<%= file_name %>.active?
+ <%= file_name %>.activate!
+ flash[:notice] = "Signup complete! Please sign in to continue."
+ redirect_to '/login'
+ when params[:activation_code].blank?
+ flash[:error] = "The activation code was missing. Please follow the URL from your email."
+ redirect_back_or_default('/')
+ else
+ flash[:error] = "We couldn't find a <%= file_name %> with that activation code -- check your email? Or maybe you've already activated -- try signing in."
+ redirect_back_or_default('/')
+ end
+ end
+<% end %><% if options[:stateful] %>
+ def suspend
+ @<%= file_name %>.suspend!
+ redirect_to <%= model_controller_routing_name %>_path
+ end
+
+ def unsuspend
+ @<%= file_name %>.unsuspend!
+ redirect_to <%= model_controller_routing_name %>_path
+ end
+
+ def destroy
+ @<%= file_name %>.delete!
+ redirect_to <%= model_controller_routing_name %>_path
+ end
+
+ def purge
+ @<%= file_name %>.destroy
+ redirect_to <%= model_controller_routing_name %>_path
+ end
+
+ # There's no page here to update or destroy a <%= file_name %>. If you add those, be
+ # smart -- make sure you check that the visitor is authorized to do so, that they
+ # supply their old password along with a new one to update it, etc.
+
+protected
+ def find_<%= file_name %>
+ @<%= file_name %> = <%= class_name %>.find(params[:id])
+ end
+<% end -%>
+end
View
93 vendor/plugins/restful_authentication/generators/authenticated/templates/model_helper.rb
@@ -0,0 +1,93 @@
+module <%= model_controller_class_name %>Helper
+
+ #
+ # 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 ('<%= table_name %>/1')
+ #
+ # By default, their login is used as link text and link title (tooltip)
+ #
+ # Takes options
+ # * :content_text => 'Content text in place of <%= file_name %>.login', escaped with
+ # the standard h() function.
+ # * :content_method => :<%= file_name %>_instance_method_to_call_for_content_text
+ # * :title_method => :<%= file_name %>_instance_method_to_call_for_title_attribute
+ # * as well as link_to()'s standard options
+ #
+ # Examples:
+ # link_to_<%= file_name %> @<%= file_name %>
+ # # => <a href="/<%= table_name %>/3" title="barmy">barmy</a>
+ #
+ # # if you've added a .name attribute:
+ # content_tag :span, :class => :vcard do
+ # (link_to_<%= file_name %> <%= file_name %>, :class => 'fn n', :title_method => :login, :content_method => :name) +
+ # ': ' + (content_tag :span, <%= file_name %>.email, :class => 'email')
+ # end
+ # # => <span class="vcard"><a href="/<%= table_name %>/3" title="barmy" class="fn n">Cyril Fotheringay-Phipps</a>: <span class="email">barmy@blandings.com</span></span>
+ #