ggoodale-restful-authentication 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/CHANGELOG +68 -0
  2. data/README.textile +224 -0
  3. data/Rakefile +32 -0
  4. data/TODO +15 -0
  5. data/generators/authenticated/USAGE +1 -0
  6. data/generators/authenticated/authenticated_generator.rb +478 -0
  7. data/generators/authenticated/lib/insert_routes.rb +54 -0
  8. data/generators/authenticated/templates/_model_partial.html.erb +8 -0
  9. data/generators/authenticated/templates/activation.erb +3 -0
  10. data/generators/authenticated/templates/authenticated_system.rb +189 -0
  11. data/generators/authenticated/templates/authenticated_test_helper.rb +22 -0
  12. data/generators/authenticated/templates/controller.rb +43 -0
  13. data/generators/authenticated/templates/helper.rb +2 -0
  14. data/generators/authenticated/templates/login.html.erb +16 -0
  15. data/generators/authenticated/templates/mailer.rb +25 -0
  16. data/generators/authenticated/templates/migration.rb +26 -0
  17. data/generators/authenticated/templates/model.rb +83 -0
  18. data/generators/authenticated/templates/model_controller.rb +85 -0
  19. data/generators/authenticated/templates/model_helper.rb +93 -0
  20. data/generators/authenticated/templates/model_helper_spec.rb +158 -0
  21. data/generators/authenticated/templates/observer.rb +11 -0
  22. data/generators/authenticated/templates/signup.html.erb +19 -0
  23. data/generators/authenticated/templates/signup_notification.erb +8 -0
  24. data/generators/authenticated/templates/site_keys.rb +38 -0
  25. data/generators/authenticated/templates/spec/controllers/access_control_spec.rb +90 -0
  26. data/generators/authenticated/templates/spec/controllers/authenticated_system_spec.rb +102 -0
  27. data/generators/authenticated/templates/spec/controllers/sessions_controller_spec.rb +139 -0
  28. data/generators/authenticated/templates/spec/controllers/users_controller_spec.rb +198 -0
  29. data/generators/authenticated/templates/spec/fixtures/users.yml +60 -0
  30. data/generators/authenticated/templates/spec/helpers/users_helper_spec.rb +141 -0
  31. data/generators/authenticated/templates/spec/models/user_spec.rb +290 -0
  32. data/generators/authenticated/templates/stories/rest_auth_stories.rb +22 -0
  33. data/generators/authenticated/templates/stories/rest_auth_stories_helper.rb +81 -0
  34. data/generators/authenticated/templates/stories/steps/ra_navigation_steps.rb +49 -0
  35. data/generators/authenticated/templates/stories/steps/ra_resource_steps.rb +179 -0
  36. data/generators/authenticated/templates/stories/steps/ra_response_steps.rb +171 -0
  37. data/generators/authenticated/templates/stories/steps/user_steps.rb +153 -0
  38. data/generators/authenticated/templates/stories/users/accounts.story +186 -0
  39. data/generators/authenticated/templates/stories/users/sessions.story +134 -0
  40. data/generators/authenticated/templates/test/functional_test.rb +82 -0
  41. data/generators/authenticated/templates/test/mailer_test.rb +31 -0
  42. data/generators/authenticated/templates/test/model_functional_test.rb +93 -0
  43. data/generators/authenticated/templates/test/unit_test.rb +164 -0
  44. data/init.rb +1 -0
  45. data/lib/authentication.rb +40 -0
  46. data/lib/authentication/by_cookie_token.rb +82 -0
  47. data/lib/authentication/by_password.rb +64 -0
  48. data/lib/authorization.rb +14 -0
  49. data/lib/authorization/aasm_roles.rb +63 -0
  50. data/lib/authorization/stateful_roles.rb +62 -0
  51. data/lib/trustification.rb +14 -0
  52. data/lib/trustification/email_validation.rb +20 -0
  53. data/rails/init.rb +3 -0
  54. metadata +115 -0
@@ -0,0 +1,68 @@
1
+ h1. Internal Changes to code
2
+
3
+ As always, this is just a copy-and-pasted version of the CHANGELOG file in the source code tree.
4
+
5
+ h2. Changes for the May, 2008 version of restful-authentication
6
+
7
+ h3. Changes to user model
8
+
9
+ * recently_activated? belongs only if stateful
10
+ * Gave migration a 40-char limit on remember_token & an index on users by login
11
+ * **Much** stricter login and email validation
12
+ * put length constraints in migration too
13
+ * password in 6, 40
14
+ * salt and remember_token now much less predictability
15
+
16
+ h3. Changes to session_controller
17
+
18
+ * use uniform logout function
19
+ * use uniform remember_cookie functions
20
+ * avoid calling logged_in? which will auto-log-you-in (safe in the face of
21
+ logout! call, but idiot-proof)
22
+ * Moved reset_session into only the "now logged in" branch
23
+ ** wherever it goes, it has to be in front of the current_user= call
24
+ ** See more in README-Tradeoffs.txt
25
+ * made a place to take action on failed login attempt
26
+ * recycle login and remember_me setting on failed login
27
+ * nil'ed out the password field in 'new' view
28
+
29
+ h3. Changes to users_controller
30
+
31
+ * use uniform logout function
32
+ * use uniform remember_cookie functions
33
+ * Moved reset_session into only the "now logged in" branch
34
+ ** wherever it goes, it has to be in front of the current_user= call
35
+ ** See more in README-Tradeoffs.txt
36
+ * made the implicit login only happen for non-activationed sites
37
+ * On a failed signup, kick you back to the signin screen (but strip out the password & confirmation)
38
+ * more descriptive error messages in activate()
39
+
40
+ h3. users_helper
41
+
42
+ * link_to_user, link_to_current_user, link_to_signin_with_IP
43
+ * if_authorized(action, resource, &block) view function (with appropriate
44
+ warning)
45
+
46
+ h3. authenticated_system
47
+
48
+ * Made authorized? take optional arguments action=nil, resource=nil, *args
49
+ This makes its signature better match traditional approaches to access control
50
+ eg Reference Monitor in "Security Patterns":http://www.securitypatterns.org/patterns.html)
51
+ * authorized? should be a helper too
52
+ * added uniform logout! methods
53
+ * format.any (as found in access_denied) doesn't work until
54
+ http://dev.rubyonrails.org/changeset/8987 lands.
55
+ * cookies are now refreshed each time we cross the logged out/in barrier, as
56
+ "best":http://palisade.plynt.com/issues/2004Jul/safe-auth-practices/
57
+ "practice":http://www.owasp.org/index.php/Session_Management#Regeneration_of_Session_Tokens
58
+
59
+ h3. Other
60
+
61
+ * Used escapes <%= %> in email templates (among other reasons, so courtenay's
62
+ "'dumbass' test":http://tinyurl.com/684g9t doesn't complain)
63
+ * Added site key to generator, users.yml.
64
+ * Made site key generation idempotent in the most crude and hackish way
65
+ * 100% coverage apart from the stateful code. (needed some access_control
66
+ checks, and the http_auth stuff)
67
+ * Stories!
68
+
@@ -0,0 +1,224 @@
1
+ h1. "Restful Authentication Generator":http://github.com/technoweenie/restful-authentication
2
+
3
+ This widely-used plugin provides a foundation for securely managing user
4
+ authentication:
5
+ * Login / logout
6
+ * Secure password handling
7
+ * Account activation by validating email
8
+ * Account approval / disabling by admin
9
+ * Rudimentary hooks for authorization and access control.
10
+
11
+ Several features were updated in May, 2008.
12
+ * "Stable newer version":http://github.com/technoweenie/restful-authentication/tree/master
13
+ * "'Classic' (backward-compatible) version":http://github.com/technoweenie/restful-authentication/tree/classic
14
+ * "Experimental version":http://github.com/technoweenie/restful-authentication/tree/modular (Much more modular, needs testing & review)
15
+
16
+ !! important: if you upgrade your site, existing user account !!
17
+ !! passwords will stop working unless you use --old-passwords !!
18
+
19
+ ***************************************************************************
20
+
21
+ h2. Issue Tracker
22
+
23
+ Please submit any bugs or annoyances on the lighthouse tracker at
24
+ * "http://rails_security.lighthouseapp.com/projects/15332-restful_authentication/overview":http://rails_security.lighthouseapp.com/projects/15332-restful_authentication/overview
25
+
26
+ For anything simple enough, please github message both maintainers: Rick Olson
27
+ ("technoweenie":http://github.com/technoweenie) and Flip Kromer
28
+ ("mrflip":http://github.com/mrflip).
29
+
30
+ ***************************************************************************
31
+
32
+ h2. Documentation
33
+
34
+ This page has notes on
35
+ * "Installation":#INSTALL
36
+ * "New Features":#AWESOME
37
+ * "After installing":#POST-INSTALL
38
+
39
+ See the "wiki":http://github.com/technoweenie/restful-authentication/wikis/home
40
+ (or the notes/ directory) if you want to learn more about:
41
+
42
+ * "Extensions, Addons and Alternatives":addons such as HAML templates
43
+ * "Security Design Patterns":security-patterns with "snazzy diagram":http://github.com/technoweenie/restful-authentication/tree/master/notes/SecurityFramework.png
44
+ * [[Authentication]] -- Lets a visitor identify herself (and lay claim to her corresponding Roles and measure of Trust)
45
+ * "Trust Metrics":Trustification -- Confidence we can rely on the outcomes of this visitor's actions.
46
+ * [[Authorization]] and Policy -- Based on trust and identity, what actions may this visitor perform?
47
+ * [[Access Control]] -- How the Authorization policy is actually enforced in your code (A: hopefully without turning it into a spaghetti of if thens)
48
+ * [[Rails Plugins]] for Authentication, Trust, Authorization and Access Control
49
+ * [[Tradeoffs]] -- for the paranoid or the curious, a rundown of tradeoffs made in the code
50
+ * [[CHANGELOG]] -- Summary of changes to internals
51
+ * [[TODO]] -- Ideas for how you can help
52
+
53
+ These best version of the release notes are in the notes/ directory in the
54
+ "source code":http://github.com/technoweenie/restful-authentication/tree/master
55
+ -- look there for the latest version. The wiki versions are taken (manually)
56
+ from there.
57
+
58
+ ***************************************************************************
59
+
60
+ <a id="AWESOME"/> </a>
61
+ h2. Exciting new features
62
+
63
+ h3. Stories
64
+
65
+ There are now RSpec stories that allow expressive, enjoyable tests for the
66
+ authentication code. The flexible code for resource testing in stories was
67
+ extended from "Ben Mabey's.":http://www.benmabey.com/2008/02/04/rspec-plain-text-stories-webrat-chunky-bacon/
68
+
69
+ h3. Modularize to match security design patterns:
70
+
71
+ * Authentication (currently: password, browser cookie token, HTTP basic)
72
+ * Trust metric (email validation)
73
+ * Authorization (stateful roles)
74
+ * Leave a flexible framework that will play nicely with other access control / policy definition / trust metric plugins
75
+
76
+ h3. Other
77
+
78
+ * Added a few helper methods for linking to user pages
79
+ * Uniform handling of logout, remember_token
80
+ * Stricter email, login field validation
81
+ * Minor security fixes -- see CHANGELOG
82
+
83
+ ***************************************************************************
84
+
85
+ h2. Non-backwards compatible Changes
86
+
87
+ Here are a few changes in the May 2008 release that increase "Defense in Depth"
88
+ but may require changes to existing accounts
89
+
90
+ * If you have an existing site, none of these changes are compelling enough to
91
+ warrant migrating your userbase.
92
+ * If you are generating for a new site, all of these changes are low-impact.
93
+ You should apply them.
94
+
95
+ h3. Passwords
96
+
97
+ The new password encryption (using a site key salt and stretching) will break
98
+ existing user accounts' passwords. We recommend you use the --old-passwords
99
+ option or write a migration tool and submit it as a patch. See the
100
+ [[Tradeoffs]] note for more information.
101
+
102
+ h3. Validations
103
+
104
+ By default, email and usernames are validated against a somewhat strict pattern; your users' values may be now illegal. Adjust to suit.
105
+
106
+ ***************************************************************************
107
+
108
+ <a id="INSTALL"/> </a>
109
+ h2. Installation
110
+
111
+ This is a basic restful authentication generator for rails, taken from
112
+ acts as authenticated. Currently it requires Rails 1.2.6 or above.
113
+
114
+ **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:
115
+ * either use <code>git clone git://github.com/technoweenie/restful-authentication.git restful_authentication</code>
116
+ * or rename the plugin's directory to be <code>restful_authentication</code> after fetching it.
117
+
118
+ To use the generator:
119
+
120
+ ./script/generate authenticated user sessions \
121
+ --include-activation \
122
+ --stateful \
123
+ --rspec \
124
+ --skip-migration \
125
+ --skip-routes \
126
+ --old-passwords
127
+
128
+ * The first parameter specifies the model that gets created in signup (typically
129
+ a user or account model). A model with migration is created, as well as a
130
+ basic controller with the create method. You probably want to say "User" here.
131
+
132
+ * The second parameter specifies the session controller name. This is the
133
+ controller that handles the actual login/logout function on the site.
134
+ (probably: "Session").
135
+
136
+ * --include-activation: Generates the code for a ActionMailer and its respective
137
+ Activation Code through email.
138
+
139
+ * --stateful: Builds in support for acts_as_state_machine and generates
140
+ activation code. (@--stateful@ implies @--include-activation@). Based on the
141
+ idea at [[http://www.vaporbase.com/postings/stateful_authentication]]. Passing
142
+ @--skip-migration@ will skip the user migration, and @--skip-routes@ will skip
143
+ resource generation -- both useful if you've already run this generator.
144
+ (Needs the "acts_as_state_machine plugin":http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/,
145
+ but new installs should probably run with @--aasm@ instead.)
146
+
147
+ * --aasm: Works the same as stateful but uses the "updated aasm gem":http://github.com/rubyist/aasm/tree/master
148
+
149
+ * --rspec: Generate RSpec tests and Stories in place of standard rails tests.
150
+ This requires the
151
+ "RSpec and Rspec-on-rails plugins":http://rspec.info/
152
+ (make sure you "./script/generate rspec" after installing RSpec.) The rspec
153
+ and story suite are much more thorough than the rails tests, and changes are
154
+ unlikely to be backported.
155
+
156
+ * --old-passwords: Use the older password scheme (see [[#COMPATIBILITY]], above)
157
+
158
+ * --skip-migration: Don't generate a migration file for this model
159
+
160
+ * --skip-routes: Don't generate a resource line in @config/routes.rb@
161
+
162
+ ***************************************************************************
163
+ <a id="POST-INSTALL"/> </a>
164
+ h2. After installing
165
+
166
+ The below assumes a Model named 'User' and a Controller named 'Session'; please
167
+ alter to suit. There are additional security minutae in @notes/README-Tradeoffs@
168
+ -- only the paranoid or the curious need bother, though.
169
+
170
+ * Add these familiar login URLs to your @config/routes.rb@ if you like:
171
+
172
+ <pre><code>
173
+ map.signup '/signup', :controller => 'users', :action => 'new'
174
+ map.login '/login', :controller => 'session', :action => 'new'
175
+ map.logout '/logout', :controller => 'session', :action => 'destroy'
176
+ </code></pre>
177
+
178
+ * With @--include-activation@, also add to your @config/routes.rb@:
179
+
180
+ <pre><code>
181
+ map.activate '/activate/:activation_code', :controller => 'users', :action => 'activate', :activation_code => nil
182
+ </code></pre>
183
+
184
+ and add an observer to @config/environment.rb@:
185
+
186
+ <pre><code>
187
+ config.active_record.observers = :user_observer
188
+ </code></pre>
189
+
190
+ Pay attention, may be this is not an issue for everybody, but if you should
191
+ have problems, that the sent activation_code does match with that in the
192
+ database stored, reload your user object before sending its data through email
193
+ something like:
194
+
195
+ <pre><code>
196
+ class UserObserver < ActiveRecord::Observer
197
+ def after_create(user)
198
+ user.reload
199
+ UserMailer.deliver_signup_notification(user)
200
+ end
201
+ def after_save(user)
202
+ user.reload
203
+ UserMailer.deliver_activation(user) if user.recently_activated?
204
+ end
205
+ end
206
+ </code></pre>
207
+
208
+
209
+ * With @--stateful@, add an observer to config/environment.rb:
210
+
211
+ <pre><code>
212
+ config.active_record.observers = :user_observer
213
+ </code></pre>
214
+
215
+ and modify the users resource line to read
216
+
217
+ map.resources :users, :member => { :suspend => :put,
218
+ :unsuspend => :put,
219
+ :purge => :delete }
220
+
221
+ * If you use a public repository for your code (such as github, rubyforge,
222
+ gitorious, etc.) make sure to NOT post your site_keys.rb (add a line like
223
+ '/config/initializers/site_keys.rb' to your .gitignore or do the svn ignore
224
+ dance), but make sure you DO keep it backed up somewhere safe.
@@ -0,0 +1,32 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'Test the restful_authentication plugin.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation for the restful_authentication plugin.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = 'RestfulAuthentication'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ gemspec = eval(File.read("#{File.dirname(__FILE__)}/restful-authentication.gemspec"))
26
+ PKG_NAME = gemspec.name
27
+ PKG_VERSION = gemspec.version
28
+
29
+ Rake::GemPackageTask.new(gemspec) do |pkg|
30
+ pkg.need_zip = true
31
+ pkg.need_tar = true
32
+ end
data/TODO ADDED
@@ -0,0 +1,15 @@
1
+
2
+ h3. Authentication security projects for a later date
3
+
4
+
5
+ * Track 'failed logins this hour' and demand a captcha after say 5 failed logins
6
+ ("RECAPTCHA plugin.":http://agilewebdevelopment.com/plugins/recaptcha)
7
+ "De-proxy-ficate IP address": http://wiki.codemongers.com/NginxHttpRealIpModule
8
+
9
+ * Make cookie spoofing a little harder: we set the user's cookie to
10
+ (remember_token), but store digest(remember_token, request_IP). A CSRF cookie
11
+ spoofer has to then at least also spoof the user's originating IP
12
+ (see "Secure Programs HOWTO":http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/web-authentication.html)
13
+
14
+ * Log HTTP request on authentication / authorization failures
15
+ http://palisade.plynt.com/issues/2004Jul/safe-auth-practices
@@ -0,0 +1 @@
1
+ ./script/generate authenticated USERMODEL CONTROLLERNAME
@@ -0,0 +1,478 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/lib/insert_routes.rb")
2
+ require 'digest/sha1'
3
+ class AuthenticatedGenerator < Rails::Generator::NamedBase
4
+ default_options :skip_migration => false,
5
+ :skip_routes => false,
6
+ :old_passwords => false,
7
+ :include_activation => false
8
+
9
+ attr_reader :controller_name,
10
+ :controller_class_path,
11
+ :controller_file_path,
12
+ :controller_class_nesting,
13
+ :controller_class_nesting_depth,
14
+ :controller_class_name,
15
+ :controller_singular_name,
16
+ :controller_plural_name,
17
+ :controller_routing_name, # new_session_path
18
+ :controller_routing_path, # /session/new
19
+ :controller_controller_name, # sessions
20
+ :controller_file_name
21
+ alias_method :controller_table_name, :controller_plural_name
22
+ attr_reader :model_controller_name,
23
+ :model_controller_class_path,
24
+ :model_controller_file_path,
25
+ :model_controller_class_nesting,
26
+ :model_controller_class_nesting_depth,
27
+ :model_controller_class_name,
28
+ :model_controller_singular_name,
29
+ :model_controller_plural_name,
30
+ :model_controller_routing_name, # new_user_path
31
+ :model_controller_routing_path, # /users/new
32
+ :model_controller_controller_name # users
33
+ alias_method :model_controller_file_name, :model_controller_singular_name
34
+ alias_method :model_controller_table_name, :model_controller_plural_name
35
+
36
+ def initialize(runtime_args, runtime_options = {})
37
+ super
38
+
39
+ @rspec = has_rspec?
40
+
41
+ @controller_name = (args.shift || 'sessions').pluralize
42
+ @model_controller_name = @name.pluralize
43
+
44
+ # sessions controller
45
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
46
+ @controller_class_name_without_nesting, @controller_file_name, @controller_plural_name = inflect_names(base_name)
47
+ @controller_singular_name = @controller_file_name.singularize
48
+ if @controller_class_nesting.empty?
49
+ @controller_class_name = @controller_class_name_without_nesting
50
+ else
51
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
52
+ end
53
+ @controller_routing_name = @controller_singular_name
54
+ @controller_routing_path = @controller_file_path.singularize
55
+ @controller_controller_name = @controller_plural_name
56
+
57
+ # model controller
58
+ base_name, @model_controller_class_path, @model_controller_file_path, @model_controller_class_nesting, @model_controller_class_nesting_depth = extract_modules(@model_controller_name)
59
+ @model_controller_class_name_without_nesting, @model_controller_singular_name, @model_controller_plural_name = inflect_names(base_name)
60
+
61
+ if @model_controller_class_nesting.empty?
62
+ @model_controller_class_name = @model_controller_class_name_without_nesting
63
+ else
64
+ @model_controller_class_name = "#{@model_controller_class_nesting}::#{@model_controller_class_name_without_nesting}"
65
+ end
66
+ @model_controller_routing_name = @table_name
67
+ @model_controller_routing_path = @model_controller_file_path
68
+ @model_controller_controller_name = @model_controller_plural_name
69
+
70
+ load_or_initialize_site_keys()
71
+
72
+ if options[:dump_generator_attribute_names]
73
+ dump_generator_attribute_names
74
+ end
75
+ end
76
+
77
+ def manifest
78
+ recorded_session = record do |m|
79
+ # Check for class naming collisions.
80
+ m.class_collisions controller_class_path, "#{controller_class_name}Controller", # Sessions Controller
81
+ "#{controller_class_name}Helper"
82
+ m.class_collisions model_controller_class_path, "#{model_controller_class_name}Controller", # Model Controller
83
+ "#{model_controller_class_name}Helper"
84
+ m.class_collisions class_path, "#{class_name}", "#{class_name}Mailer", "#{class_name}MailerTest", "#{class_name}Observer"
85
+ m.class_collisions [], 'AuthenticatedSystem', 'AuthenticatedTestHelper'
86
+
87
+ # Controller, helper, views, and test directories.
88
+ m.directory File.join('app/models', class_path)
89
+ m.directory File.join('app/controllers', controller_class_path)
90
+ m.directory File.join('app/controllers', model_controller_class_path)
91
+ m.directory File.join('app/helpers', controller_class_path)
92
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
93
+ m.directory File.join('app/views', class_path, "#{file_name}_mailer") if options[:include_activation]
94
+
95
+ m.directory File.join('app/controllers', model_controller_class_path)
96
+ m.directory File.join('app/helpers', model_controller_class_path)
97
+ m.directory File.join('app/views', model_controller_class_path, model_controller_file_name)
98
+ m.directory File.join('config/initializers')
99
+
100
+ if @rspec
101
+ m.directory File.join('spec/controllers', controller_class_path)
102
+ m.directory File.join('spec/controllers', model_controller_class_path)
103
+ m.directory File.join('spec/models', class_path)
104
+ m.directory File.join('spec/helpers', model_controller_class_path)
105
+ m.directory File.join('spec/fixtures', class_path)
106
+ m.directory File.join('stories', model_controller_file_path)
107
+ m.directory File.join('stories', 'steps')
108
+ else
109
+ m.directory File.join('test/functional', controller_class_path)
110
+ m.directory File.join('test/functional', model_controller_class_path)
111
+ m.directory File.join('test/unit', class_path)
112
+ m.directory File.join('test/fixtures', class_path)
113
+ end
114
+
115
+ m.template 'model.rb',
116
+ File.join('app/models',
117
+ class_path,
118
+ "#{file_name}.rb")
119
+
120
+ if options[:include_activation]
121
+ %w( mailer observer ).each do |model_type|
122
+ m.template "#{model_type}.rb", File.join('app/models',
123
+ class_path,
124
+ "#{file_name}_#{model_type}.rb")
125
+ end
126
+ end
127
+
128
+ m.template 'controller.rb',
129
+ File.join('app/controllers',
130
+ controller_class_path,
131
+ "#{controller_file_name}_controller.rb")
132
+
133
+ m.template 'model_controller.rb',
134
+ File.join('app/controllers',
135
+ model_controller_class_path,
136
+ "#{model_controller_file_name}_controller.rb")
137
+
138
+ m.template 'authenticated_system.rb',
139
+ File.join('lib', 'authenticated_system.rb')
140
+
141
+ m.template 'authenticated_test_helper.rb',
142
+ File.join('lib', 'authenticated_test_helper.rb')
143
+
144
+ m.template 'site_keys.rb', site_keys_file
145
+
146
+ if @rspec
147
+ # RSpec Specs
148
+ m.template 'spec/controllers/users_controller_spec.rb',
149
+ File.join('spec/controllers',
150
+ model_controller_class_path,
151
+ "#{model_controller_file_name}_controller_spec.rb")
152
+ m.template 'spec/controllers/sessions_controller_spec.rb',
153
+ File.join('spec/controllers',
154
+ controller_class_path,
155
+ "#{controller_file_name}_controller_spec.rb")
156
+ m.template 'spec/controllers/access_control_spec.rb',
157
+ File.join('spec/controllers',
158
+ controller_class_path,
159
+ "access_control_spec.rb")
160
+ m.template 'spec/controllers/authenticated_system_spec.rb',
161
+ File.join('spec/controllers',
162
+ controller_class_path,
163
+ "authenticated_system_spec.rb")
164
+ m.template 'spec/helpers/users_helper_spec.rb',
165
+ File.join('spec/helpers',
166
+ model_controller_class_path,
167
+ "#{table_name}_helper_spec.rb")
168
+ m.template 'spec/models/user_spec.rb',
169
+ File.join('spec/models',
170
+ class_path,
171
+ "#{file_name}_spec.rb")
172
+ m.template 'spec/fixtures/users.yml',
173
+ File.join('spec/fixtures',
174
+ class_path,
175
+ "#{table_name}.yml")
176
+
177
+ # RSpec Stories
178
+ m.template 'stories/steps/ra_navigation_steps.rb',
179
+ File.join('stories/steps/ra_navigation_steps.rb')
180
+ m.template 'stories/steps/ra_response_steps.rb',
181
+ File.join('stories/steps/ra_response_steps.rb')
182
+ m.template 'stories/steps/ra_resource_steps.rb',
183
+ File.join('stories/steps/ra_resource_steps.rb')
184
+ m.template 'stories/steps/user_steps.rb',
185
+ File.join('stories/steps/', "#{file_name}_steps.rb")
186
+ m.template 'stories/users/accounts.story',
187
+ File.join('stories', model_controller_file_path, 'accounts.story')
188
+ m.template 'stories/users/sessions.story',
189
+ File.join('stories', model_controller_file_path, 'sessions.story')
190
+ m.template 'stories/rest_auth_stories_helper.rb',
191
+ File.join('stories', 'rest_auth_stories_helper.rb')
192
+ m.template 'stories/rest_auth_stories.rb',
193
+ File.join('stories', 'rest_auth_stories.rb')
194
+
195
+ else
196
+ m.template 'test/functional_test.rb',
197
+ File.join('test/functional',
198
+ controller_class_path,
199
+ "#{controller_file_name}_controller_test.rb")
200
+ m.template 'test/model_functional_test.rb',
201
+ File.join('test/functional',
202
+ model_controller_class_path,
203
+ "#{model_controller_file_name}_controller_test.rb")
204
+ m.template 'test/unit_test.rb',
205
+ File.join('test/unit',
206
+ class_path,
207
+ "#{file_name}_test.rb")
208
+ if options[:include_activation]
209
+ m.template 'test/mailer_test.rb', File.join('test/unit', class_path, "#{file_name}_mailer_test.rb")
210
+ end
211
+ m.template 'spec/fixtures/users.yml',
212
+ File.join('test/fixtures',
213
+ class_path,
214
+ "#{table_name}.yml")
215
+ end
216
+
217
+ m.template 'helper.rb',
218
+ File.join('app/helpers',
219
+ controller_class_path,
220
+ "#{controller_file_name}_helper.rb")
221
+
222
+ m.template 'model_helper.rb',
223
+ File.join('app/helpers',
224
+ model_controller_class_path,
225
+ "#{model_controller_file_name}_helper.rb")
226
+
227
+
228
+ # Controller templates
229
+ m.template 'login.html.erb', File.join('app/views', controller_class_path, controller_file_name, "new.html.erb")
230
+ m.template 'signup.html.erb', File.join('app/views', model_controller_class_path, model_controller_file_name, "new.html.erb")
231
+ m.template '_model_partial.html.erb', File.join('app/views', model_controller_class_path, model_controller_file_name, "_#{file_name}_bar.html.erb")
232
+
233
+ if options[:include_activation]
234
+ # Mailer templates
235
+ %w( activation signup_notification ).each do |action|
236
+ m.template "#{action}.erb",
237
+ File.join('app/views', "#{file_name}_mailer", "#{action}.erb")
238
+ end
239
+ end
240
+
241
+ unless options[:skip_migration]
242
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => {
243
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
244
+ }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
245
+ end
246
+ unless options[:skip_routes]
247
+ # Note that this fails for nested classes -- you're on your own with setting up the routes.
248
+ m.route_resource controller_singular_name
249
+ m.route_resources model_controller_plural_name
250
+ m.route_name('signup', '/signup', {:controller => model_controller_plural_name, :action => 'new'})
251
+ m.route_name('register', '/register', {:controller => model_controller_plural_name, :action => 'create'})
252
+ m.route_name('login', '/login', {:controller => controller_controller_name, :action => 'new'})
253
+ m.route_name('logout', '/logout', {:controller => controller_controller_name, :action => 'destroy'})
254
+ end
255
+ end
256
+
257
+ #
258
+ # Post-install notes
259
+ #
260
+ action = File.basename($0) # grok the action from './script/generate' or whatever
261
+ case action
262
+ when "generate"
263
+ puts "Ready to generate."
264
+ puts ("-" * 70)
265
+ puts "Once finished, don't forget to:"
266
+ puts
267
+ if options[:include_activation]
268
+ puts "- Add an observer to config/environment.rb"
269
+ puts " config.active_record.observers = :#{file_name}_observer"
270
+ end
271
+ if options[:aasm]
272
+ puts "- Install the acts_as_state_machine gem:"
273
+ puts " sudo gem sources -a http://gems.github.com (If you haven't already)"
274
+ puts " sudo gem install rubyist-aasm"
275
+ elsif options[:stateful]
276
+ puts "- Install the acts_as_state_machine plugin:"
277
+ puts " svn export http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk vendor/plugins/acts_as_state_machine"
278
+ end
279
+ puts "- Add routes to these resources. In config/routes.rb, insert routes like:"
280
+ puts %( map.signup '/signup', :controller => '#{model_controller_file_name}', :action => 'new')
281
+ puts %( map.login '/login', :controller => '#{controller_file_name}', :action => 'new')
282
+ puts %( map.logout '/logout', :controller => '#{controller_file_name}', :action => 'destroy')
283
+ if options[:include_activation]
284
+ puts %( map.activate '/activate/:activation_code', :controller => '#{model_controller_file_name}', :action => 'activate', :activation_code => nil)
285
+ end
286
+ if options[:stateful]
287
+ puts " and modify the map.resources :#{model_controller_file_name} line to include these actions:"
288
+ puts " map.resources :#{model_controller_file_name}, :member => { :suspend => :put, :unsuspend => :put, :purge => :delete }"
289
+ end
290
+ puts
291
+ puts ("-" * 70)
292
+ puts
293
+ if $rest_auth_site_key_from_generator.blank?
294
+ puts "You've set a nil site key. This preserves existing users' passwords,"
295
+ puts "but allows dictionary attacks in the unlikely event your database is"
296
+ puts "compromised and your site code is not. See the README for more."
297
+ elsif $rest_auth_keys_are_new
298
+ puts "We've create a new site key in #{site_keys_file}. If you have existing"
299
+ puts "user accounts their passwords will no longer work (see README). As always,"
300
+ puts "keep this file safe but don't post it in public."
301
+ else
302
+ puts "We've reused the existing site key in #{site_keys_file}. As always,"
303
+ puts "keep this file safe but don't post it in public."
304
+ end
305
+ puts
306
+ puts ("-" * 70)
307
+ when "destroy"
308
+ puts
309
+ puts ("-" * 70)
310
+ puts
311
+ puts "Thanks for using restful_authentication"
312
+ puts
313
+ puts "Don't forget to comment out the observer line in environment.rb"
314
+ puts " (This was optional so it may not even be there)"
315
+ puts " # config.active_record.observers = :#{file_name}_observer"
316
+ puts
317
+ puts ("-" * 70)
318
+ puts
319
+ else
320
+ puts "Didn't understand the action '#{action}' -- you might have missed the 'after running me' instructions."
321
+ end
322
+
323
+ #
324
+ # Do the thing
325
+ #
326
+ recorded_session
327
+ end
328
+
329
+ def has_rspec?
330
+ spec_dir = File.join(RAILS_ROOT, 'spec')
331
+ options[:rspec] ||= (File.exist?(spec_dir) && File.directory?(spec_dir)) unless (options[:rspec] == false)
332
+ end
333
+
334
+ #
335
+ # !! These must match the corresponding routines in by_password.rb !!
336
+ #
337
+ def secure_digest(*args)
338
+ Digest::SHA1.hexdigest(args.flatten.join('--'))
339
+ end
340
+ def make_token
341
+ secure_digest(Time.now, (1..10).map{ rand.to_s })
342
+ end
343
+ def password_digest(password, salt)
344
+ digest = $rest_auth_site_key_from_generator
345
+ $rest_auth_digest_stretches_from_generator.times do
346
+ digest = secure_digest(digest, salt, password, $rest_auth_site_key_from_generator)
347
+ end
348
+ digest
349
+ end
350
+
351
+ #
352
+ # Try to be idempotent:
353
+ # pull in the existing site key if any,
354
+ # seed it with reasonable defaults otherwise
355
+ #
356
+ def load_or_initialize_site_keys
357
+ case
358
+ when defined? REST_AUTH_SITE_KEY
359
+ if (options[:old_passwords]) && ((! REST_AUTH_SITE_KEY.blank?) || (REST_AUTH_DIGEST_STRETCHES != 1))
360
+ 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."
361
+ end
362
+ $rest_auth_site_key_from_generator = REST_AUTH_SITE_KEY
363
+ $rest_auth_digest_stretches_from_generator = REST_AUTH_DIGEST_STRETCHES
364
+ when options[:old_passwords]
365
+ $rest_auth_site_key_from_generator = nil
366
+ $rest_auth_digest_stretches_from_generator = 1
367
+ $rest_auth_keys_are_new = true
368
+ else
369
+ $rest_auth_site_key_from_generator = make_token
370
+ $rest_auth_digest_stretches_from_generator = 10
371
+ $rest_auth_keys_are_new = true
372
+ end
373
+ end
374
+ def site_keys_file
375
+ File.join("config", "initializers", "site_keys.rb")
376
+ end
377
+
378
+ protected
379
+ # Override with your own usage banner.
380
+ def banner
381
+ "Usage: #{$0} authenticated ModelName [ControllerName]"
382
+ end
383
+
384
+ def add_options!(opt)
385
+ opt.separator ''
386
+ opt.separator 'Options:'
387
+ opt.on("--skip-migration",
388
+ "Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
389
+ opt.on("--include-activation",
390
+ "Generate signup 'activation code' confirmation via email") { |v| options[:include_activation] = true }
391
+ opt.on("--stateful",
392
+ "Use acts_as_state_machine. Assumes --include-activation") { |v| options[:include_activation] = options[:stateful] = true }
393
+ opt.on("--aasm",
394
+ "Use (gem) aasm. Assumes --include-activation") { |v| options[:include_activation] = options[:stateful] = options[:aasm] = true }
395
+ opt.on("--rspec",
396
+ "Force rspec mode (checks for RAILS_ROOT/spec by default)") { |v| options[:rspec] = true }
397
+ opt.on("--no-rspec",
398
+ "Force test (not RSpec mode") { |v| options[:rspec] = false }
399
+ opt.on("--skip-routes",
400
+ "Don't generate a resource line in config/routes.rb") { |v| options[:skip_routes] = v }
401
+ opt.on("--old-passwords",
402
+ "Use the older password encryption scheme (see README)") { |v| options[:old_passwords] = v }
403
+ opt.on("--dump-generator-attrs",
404
+ "(generator debug helper)") { |v| options[:dump_generator_attribute_names] = v }
405
+ end
406
+
407
+ def dump_generator_attribute_names
408
+ generator_attribute_names = [
409
+ :table_name,
410
+ :file_name,
411
+ :class_name,
412
+ :controller_name,
413
+ :controller_class_path,
414
+ :controller_file_path,
415
+ :controller_class_nesting,
416
+ :controller_class_nesting_depth,
417
+ :controller_class_name,
418
+ :controller_singular_name,
419
+ :controller_plural_name,
420
+ :controller_routing_name, # new_session_path
421
+ :controller_routing_path, # /session/new
422
+ :controller_controller_name, # sessions
423
+ :controller_file_name,
424
+ :controller_table_name, :controller_plural_name,
425
+ :model_controller_name,
426
+ :model_controller_class_path,
427
+ :model_controller_file_path,
428
+ :model_controller_class_nesting,
429
+ :model_controller_class_nesting_depth,
430
+ :model_controller_class_name,
431
+ :model_controller_singular_name,
432
+ :model_controller_plural_name,
433
+ :model_controller_routing_name, # new_user_path
434
+ :model_controller_routing_path, # /users/new
435
+ :model_controller_controller_name, # users
436
+ :model_controller_file_name, :model_controller_singular_name,
437
+ :model_controller_table_name, :model_controller_plural_name,
438
+ ]
439
+ generator_attribute_names.each do |attr|
440
+ puts "%-40s %s" % ["#{attr}:", self.send(attr)] # instance_variable_get("@#{attr.to_s}"
441
+ end
442
+
443
+ end
444
+ end
445
+
446
+ # ./script/generate authenticated FoonParent::Foon SporkParent::Spork -p --force --rspec --dump-generator-attrs
447
+ # table_name: foon_parent_foons
448
+ # file_name: foon
449
+ # class_name: FoonParent::Foon
450
+ # controller_name: SporkParent::Sporks
451
+ # controller_class_path: spork_parent
452
+ # controller_file_path: spork_parent/sporks
453
+ # controller_class_nesting: SporkParent
454
+ # controller_class_nesting_depth: 1
455
+ # controller_class_name: SporkParent::Sporks
456
+ # controller_singular_name: spork
457
+ # controller_plural_name: sporks
458
+ # controller_routing_name: spork
459
+ # controller_routing_path: spork_parent/spork
460
+ # controller_controller_name: sporks
461
+ # controller_file_name: sporks
462
+ # controller_table_name: sporks
463
+ # controller_plural_name: sporks
464
+ # model_controller_name: FoonParent::Foons
465
+ # model_controller_class_path: foon_parent
466
+ # model_controller_file_path: foon_parent/foons
467
+ # model_controller_class_nesting: FoonParent
468
+ # model_controller_class_nesting_depth: 1
469
+ # model_controller_class_name: FoonParent::Foons
470
+ # model_controller_singular_name: foons
471
+ # model_controller_plural_name: foons
472
+ # model_controller_routing_name: foon_parent_foons
473
+ # model_controller_routing_path: foon_parent/foons
474
+ # model_controller_controller_name: foons
475
+ # model_controller_file_name: foons
476
+ # model_controller_singular_name: foons
477
+ # model_controller_table_name: foons
478
+ # model_controller_plural_name: foons