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