sparkly-auth 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +17 -0
  3. data/Rakefile +50 -0
  4. data/VERSION +1 -0
  5. data/app/controllers/sparkly_accounts_controller.rb +59 -0
  6. data/app/controllers/sparkly_controller.rb +47 -0
  7. data/app/controllers/sparkly_sessions_controller.rb +52 -0
  8. data/app/models/password.rb +3 -0
  9. data/app/models/remembrance_token.rb +50 -0
  10. data/app/views/sparkly_accounts/edit.html.erb +24 -0
  11. data/app/views/sparkly_accounts/new.html.erb +24 -0
  12. data/app/views/sparkly_accounts/show.html.erb +0 -0
  13. data/app/views/sparkly_sessions/new.html.erb +22 -0
  14. data/dependencies.rb +1 -0
  15. data/generators/sparkly/USAGE +27 -0
  16. data/generators/sparkly/sparkly_generator.rb +76 -0
  17. data/generators/sparkly/templates/accounts_controller.rb +65 -0
  18. data/generators/sparkly/templates/accounts_helper.rb +2 -0
  19. data/generators/sparkly/templates/help_file.txt +56 -0
  20. data/generators/sparkly/templates/initializer.rb +30 -0
  21. data/generators/sparkly/templates/migrations/add_confirmed_to_sparkly_passwords.rb +9 -0
  22. data/generators/sparkly/templates/migrations/create_sparkly_passwords.rb +19 -0
  23. data/generators/sparkly/templates/migrations/create_sparkly_remembered_tokens.rb +15 -0
  24. data/generators/sparkly/templates/sessions_controller.rb +45 -0
  25. data/generators/sparkly/templates/sessions_helper.rb +2 -0
  26. data/generators/sparkly/templates/tasks/migrations.rb +1 -0
  27. data/generators/sparkly/templates/views/sparkly_accounts/edit.html.erb +24 -0
  28. data/generators/sparkly/templates/views/sparkly_accounts/new.html.erb +24 -0
  29. data/generators/sparkly/templates/views/sparkly_accounts/show.html.erb +0 -0
  30. data/generators/sparkly/templates/views/sparkly_sessions/new.html.erb +22 -0
  31. data/init.rb +44 -0
  32. data/lib/auth.rb +52 -0
  33. data/lib/auth/behavior/base.rb +64 -0
  34. data/lib/auth/behavior/core.rb +87 -0
  35. data/lib/auth/behavior/core/authenticated_model_methods.rb +52 -0
  36. data/lib/auth/behavior/core/controller_extensions.rb +52 -0
  37. data/lib/auth/behavior/core/controller_extensions/class_methods.rb +24 -0
  38. data/lib/auth/behavior/core/controller_extensions/current_user.rb +54 -0
  39. data/lib/auth/behavior/core/password_methods.rb +65 -0
  40. data/lib/auth/behavior/remember_me.rb +17 -0
  41. data/lib/auth/behavior/remember_me/configuration.rb +21 -0
  42. data/lib/auth/behavior/remember_me/controller_extensions.rb +66 -0
  43. data/lib/auth/behavior_lookup.rb +10 -0
  44. data/lib/auth/configuration.rb +328 -0
  45. data/lib/auth/encryptors/sha512.rb +20 -0
  46. data/lib/auth/generators/configuration_generator.rb +20 -0
  47. data/lib/auth/generators/controllers_generator.rb +34 -0
  48. data/lib/auth/generators/migration_generator.rb +32 -0
  49. data/lib/auth/generators/route_generator.rb +19 -0
  50. data/lib/auth/generators/views_generator.rb +26 -0
  51. data/lib/auth/model.rb +94 -0
  52. data/lib/auth/observer.rb +21 -0
  53. data/lib/auth/target_list.rb +5 -0
  54. data/lib/auth/tasks/migrations.rb +71 -0
  55. data/lib/auth/token.rb +10 -0
  56. data/lib/sparkly-auth.rb +1 -0
  57. data/rails/init.rb +17 -0
  58. data/rails/routes.rb +19 -0
  59. data/sparkly-auth.gemspec +143 -0
  60. data/spec/controllers/application_controller_spec.rb +13 -0
  61. data/spec/generators/sparkly_spec.rb +64 -0
  62. data/spec/lib/auth/behavior/core_spec.rb +184 -0
  63. data/spec/lib/auth/behavior/remember_me_spec.rb +127 -0
  64. data/spec/lib/auth/extensions/controller_spec.rb +32 -0
  65. data/spec/lib/auth/model_spec.rb +57 -0
  66. data/spec/lib/auth_spec.rb +32 -0
  67. data/spec/mocks/models/user.rb +3 -0
  68. data/spec/routes_spec.rb +24 -0
  69. data/spec/spec_helper.rb +61 -0
  70. data/spec/views_spec.rb +18 -0
  71. metadata +210 -0
@@ -0,0 +1,65 @@
1
+ class <%=model.accounts_controller.camelize%>Controller < SparklyController
2
+ require_login_for :show, :edit, :update, :destroy
3
+
4
+ # GET new_model_url
5
+ def new
6
+ end
7
+
8
+ # POST model_url
9
+ def create
10
+ if model.save
11
+ login!(model)
12
+ redirect_back_or_default Auth.default_destination, Auth.account_created_message
13
+ else
14
+ render :action => 'new'
15
+ end
16
+ end
17
+
18
+ # GET model_url
19
+ def show
20
+ end
21
+
22
+ # GET edit_model_url
23
+ def edit
24
+ end
25
+
26
+ # PUT model_url
27
+ def update
28
+ if !model_params[:password].blank? || !model_params[:password_confirmation].blank?
29
+ model.password = model_params[:password]
30
+ model.password_confirmation = model_params[:password_confirmation]
31
+ end
32
+
33
+ if model.save
34
+ redirect_back_or_default user_path, Auth.account_updated_message
35
+ else
36
+ render :action => 'edit'
37
+ end
38
+ end
39
+
40
+ # DELETE model_url
41
+ def destroy
42
+ current_user && current_user.destroy
43
+ logout!
44
+ @current_user = nil
45
+ flash[:notice] = Auth.account_deleted_message
46
+ redirect_back_or_default Auth.default_destination
47
+ end
48
+
49
+ protected
50
+ def find_user_model
51
+ # password fields are protected attrs, so we need to exclude them then add them explicitly.
52
+ self.model_instance = current_user ||
53
+ returning(model_class.new(model_params.without(:password, :password_confirmation))) { |model|
54
+ model.password = model_params[:password]
55
+ model.password_confirmation = model_params[:password_confirmation]
56
+ }
57
+ end
58
+
59
+ # Uncomment if you don't trust the params[:model] set up by Sparkly routing, or if you've
60
+ # disabled them.
61
+ #
62
+ #def model_name
63
+ # <%=model.name.inspect%>
64
+ #end
65
+ end
@@ -0,0 +1,2 @@
1
+ module <%=model.accounts_controller.camelize%>Helper
2
+ end
@@ -0,0 +1,56 @@
1
+ I'll assume you know what Sparkly Authentication is since it seems
2
+ to be installed. If that assumption is incorrect, you should check
3
+ out the Sparkly Authentication readme instead.
4
+
5
+ So let's get right into usage. This text was generated by the Sparkly
6
+ command-line generator, invoked via:
7
+
8
+ script/generate sparkly help
9
+
10
+ Depending on what arguments are attached, this generator is capable
11
+ of producing various different results. So let's go through them one
12
+ at a time, in the most common order...
13
+
14
+ 0. Usually the first thing you'll want to do is generate the models
15
+ which will actually be authenticated, such as a User model. See
16
+ the Rails Guides for more details on that. You don't need to
17
+ actually run the migrations yet, however.
18
+
19
+ 1. After you know which models will be authenticated, you're ready
20
+ to invoke the Sparkly Config generator:
21
+
22
+ script/generate sparkly config
23
+
24
+ This will generate a Rails Initializer in config/initializers that
25
+ will be used to set up Sparkly during runtime. This tells it what
26
+ encryption type to use, which models to authenticate, and so on.
27
+ You should take a look at this file to make sure the configuration
28
+ is what you are expecting. Do that. Now.
29
+
30
+ 2. You also need to generate the database table which stores the
31
+ password information -- I'm talking about migrations!
32
+
33
+ script/generate sparkly migrations
34
+
35
+ 4. Run the server and try it out. See how things feel. If you want
36
+ more control over the views (and you should), you can generate
37
+ them like so:
38
+
39
+ script/generate sparkly views
40
+
41
+ Their final resting place basically depends on what your Sparkly
42
+ config from Step 1 looks like. So I hope you double checked it.
43
+
44
+ 5. Finally, if you need control over the, er, controllers, you can go
45
+ ahead and generate them like so:
46
+
47
+ script/generate sparkly controllers
48
+
49
+ Note that exactly which controllers and how many of them will be
50
+ generated depends, once again, on your Sparkly config. Note also
51
+ that this will generate the corresponding views for you, so you
52
+ can skip step 4 if you already know you need to customize the
53
+ controllers.
54
+
55
+ This file has been saved to doc/sparkly_authentication.txt for your
56
+ reference.
@@ -0,0 +1,30 @@
1
+ # This file sets up Sparkly Auth to work properly with Rails. It was generated
2
+ # by "script/generate sparkly config" and can be regenerated with that command, though
3
+ # you may not want to actually do that if you've made changes to this file.
4
+ #
5
+ # You are also HIGHLY encouraged to check out the Auth::Configuration class documentation
6
+ # for a list of all the options you can set here. There are a LOT of them.
7
+ #
8
+ Auth.configure do |config|
9
+ config.authenticate :user
10
+ # Adds a model to be authenticated. See the Auth::Model class for information on
11
+ # what options you can pass. Here are some common examples:
12
+ #
13
+ # config.authenticate :user, :accounts_controller => "users", :sessions_controller => "user_sessions"
14
+ # config.authenticate :user, :key => "login"
15
+ #
16
+ # By default, :key is "email" and the controllers are Sparkly's internal controllers.
17
+ # (Don't forget you can also script/generate controllers or script/generate views to
18
+ # remove the overhead of setting up your own.)
19
+ #
20
+
21
+ # You can also configure the various behaviors (as long as they support configurations):
22
+ # config.remember_me.token_theft_message =
23
+ # "Your account may have been hijacked recently! Verify that all settings are correct."
24
+ #
25
+ # config.remember_me.duration = 6.months
26
+ #
27
+ # See the class documentation for the behaviors' configurations themselves for details
28
+ # about these options. (For example, see Auth::Behaviors::RememberMe::Configuration for
29
+ # the Remember Me configuration options.)
30
+ end
@@ -0,0 +1,9 @@
1
+ class AddConfirmedToSparklyPasswords < ActiveRecord::Migration
2
+ def self.up
3
+ add_column :passwords, :confirmed, :boolean
4
+ end
5
+
6
+ def self.down
7
+ remove_column :passwords, :confirmed
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class CreateSparklyPasswords < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :passwords do |t|
4
+ t.string :secret
5
+ t.string :salt
6
+
7
+ t.string :persistence_token # the token stored in cookies to persist the user's session
8
+ t.string :single_access_token # used to authenticate a user for a single request. This is not persisted.
9
+ t.string :perishable_token # used in confirming an account, usually via email
10
+
11
+ t.references :authenticatable, :polymorphic => true
12
+ t.timestamps
13
+ end
14
+ end
15
+
16
+ def self.down
17
+ drop_table :passwords
18
+ end
19
+ end
@@ -0,0 +1,15 @@
1
+ class CreateSparklyRememberedTokens < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :remembrance_tokens do |t|
4
+ t.string :series_token
5
+ t.string :remembrance_token
6
+
7
+ t.references :authenticatable, :polymorphic => true
8
+ t.timestamps
9
+ end
10
+ end
11
+
12
+ def self.down
13
+ drop_table :remembrance_tokens
14
+ end
15
+ end
@@ -0,0 +1,45 @@
1
+ class <%=model.sessions_controller.camelize%>Controller < SparklyController
2
+ # GET new_model_session_url
3
+ def new
4
+ end
5
+
6
+ # POST model_session_url
7
+ def create
8
+ if session[:locked_out_at] && session[:locked_out_at] > Auth.account_lock_duration.ago
9
+ flash[:error] = Auth.account_locked_message
10
+ render :action => 'new'
11
+ return
12
+ end
13
+
14
+ model = model_class.find(:first, :conditions => { model_config.key => model_params[model_config.key] },
15
+ :include => :passwords)
16
+
17
+ if model && model.password_matches?(model_params[:password])
18
+ login! model
19
+ redirect_back_or_default Auth.default_destination, Auth.login_successful_message
20
+ else
21
+ session[:login_failures] = session[:login_failures].to_i + 1
22
+ if Auth.max_login_failures && session[:login_failures] >= Auth.max_login_failures
23
+ session[:locked_out_at] = Time.now
24
+ flash[:error] = Auth.account_locked_message
25
+ else
26
+ flash[:error] = Auth.invalid_credentials_message
27
+ end
28
+ render :action => "new"
29
+ end
30
+ end
31
+
32
+ # DELETE model_session_url
33
+ def destroy
34
+ logout!
35
+ redirect_back_or_default Auth.default_destination, Auth.logout_message
36
+ end
37
+
38
+ protected
39
+ # Uncomment if you don't trust the params[:model] set up by Sparkly routing, or if you've
40
+ # disabled them.
41
+ #
42
+ #def model_name
43
+ # <%=model.name.inspect%>
44
+ #end
45
+ end
@@ -0,0 +1,2 @@
1
+ module <%=model.sessions_controller.camelize%>Helper
2
+ end
@@ -0,0 +1 @@
1
+ require 'auth/tasks/migrations'
@@ -0,0 +1,24 @@
1
+ <%form_for model, :url => model_path do |f|%>
2
+ <p>
3
+ <%=f.error_messages%>
4
+ </p>
5
+
6
+ <p>
7
+ <%=f.label model_config.key%><br/>
8
+ <%=f.text_field model_config.key%>
9
+ </p>
10
+
11
+ <p>
12
+ <%=f.label :password%><br/>
13
+ <%=f.password_field :password, :value => ''%>
14
+ </p>
15
+
16
+ <p>
17
+ <%=f.label :password_confirmation%><br/>
18
+ <%=f.password_field :password_confirmation, :value => ''%>
19
+ </p>
20
+
21
+ <p>
22
+ <%=f.submit "Update Profile"%>
23
+ </p>
24
+ <%end%>
@@ -0,0 +1,24 @@
1
+ <%form_for model, :url => model_path do |f|%>
2
+ <p>
3
+ <%=f.error_messages%>
4
+ </p>
5
+
6
+ <p>
7
+ <%=f.label model_config.key%><br/>
8
+ <%=f.text_field model_config.key%>
9
+ </p>
10
+
11
+ <p>
12
+ <%=f.label :password%><br/>
13
+ <%=f.password_field :password, :value => ''%>
14
+ </p>
15
+
16
+ <p>
17
+ <%=f.label :password_confirmation%><br/>
18
+ <%=f.password_field :password_confirmation, :value => ''%>
19
+ </p>
20
+
21
+ <p>
22
+ <%=f.submit "Sign up"%>
23
+ </p>
24
+ <%end%>
@@ -0,0 +1,22 @@
1
+ <%form_for model, :url => model_session_path do |f|%>
2
+ <p>
3
+ <%=f.label model_config.key%><br/>
4
+ <%=f.text_field model_config.key%>
5
+ </p>
6
+
7
+ <p>
8
+ <%=f.label :password%><br/>
9
+ <%=f.password_field :password, :value => ''%>
10
+ </p>
11
+
12
+ <%if Auth.remember_me.enabled?%>
13
+ <p>
14
+ <%=f.check_box :remember_me, :checked => false%>
15
+ <%=f.label :remember_me%>
16
+ </p>
17
+ <%end%>
18
+
19
+ <p>
20
+ <%=f.submit "Sign in"%>
21
+ </p>
22
+ <%end%>
data/init.rb ADDED
@@ -0,0 +1,44 @@
1
+ require File.expand_path("../dependencies", __FILE__)
2
+ require File.expand_path("../lib/auth", __FILE__)
3
+
4
+ $LOAD_PATH << Auth.path
5
+ ActiveSupport::Dependencies.load_paths << Auth.path
6
+ ActiveSupport::Dependencies.load_once_paths << Auth.path
7
+ #ActiveSupport::Dependencies.load_once_paths.delete(Auth.path)
8
+
9
+ # Kick auth after initialize and do it again before every request in development
10
+ Rails.configuration.to_prepare do
11
+ Auth.kick!
12
+ end
13
+
14
+ # FIXME HACK extension to ActiveRecord::Errors to allow error attributes to be renamed. This is
15
+ # because otherwise we'll end up producing very confusing error messages complaining about :secret,
16
+ # :encrypted_secret, and so forth when all the user really cares about is :password.
17
+ class ActiveRecord::Errors
18
+ def rename_attribute(original_name, new_name)
19
+ original_name, new_name = original_name.to_s, new_name.to_s
20
+ return if original_name == new_name
21
+
22
+ @errors.each do |attribute, old_errors|
23
+ if attribute.to_s == original_name
24
+ original_name = attribute # because some are strings, some are symbols.
25
+
26
+ old_errors.each do |error|
27
+ new_error = error.dup
28
+ new_error.attribute = new_name
29
+ unless @errors[new_name] && @errors[new_name].include?(new_error)
30
+ @errors[new_name] ||= []
31
+ @errors[new_name] << new_error
32
+ end
33
+ end
34
+ @errors.delete(original_name)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ class ActiveRecord::Error
41
+ def ==(other)
42
+ other.kind_of?(ActiveRecord::Error) && attribute.to_s == other.attribute.to_s && message == other.message
43
+ end
44
+ end
data/lib/auth.rb ADDED
@@ -0,0 +1,52 @@
1
+ module Auth
2
+ class << self
3
+ public :delegate
4
+ delegate :path, :encryptor, :default_accounts_controller_name, :default_sessions_controller_name,
5
+ :password_update_frequency, :base_controller, :login_required_message, :logout_required_message,
6
+ :default_destination, :session_duration, :invalid_credentials_message, :login_successful_message,
7
+ :logout_message, :session_timeout_message, :default_login_path, :account_deleted_message,
8
+ :account_created_message, :account_updated_message, :account_locked_message, :max_login_failures,
9
+ :generate_routes?, :disable_route_generation!, :password_uniqueness_message,
10
+ :password_history_length, :base_controller_name, :account_lock_duration,
11
+ :password_format, :password_format_message, :minimum_password_length, :behaviors, :behavior_classes,
12
+ :to => :configuration
13
+
14
+ def configuration
15
+ @configuration ||= Auth::Configuration.new
16
+ end
17
+
18
+ def configure
19
+ yield configuration
20
+ end
21
+
22
+ # Applies all configuration settings. This is done by the Auth system after it has been configured but before
23
+ # it processes any requests.
24
+ def configure!
25
+ begin
26
+ configuration.apply!
27
+ rescue NameError
28
+ puts
29
+ puts "WARNING: #{$!.message}"
30
+ puts
31
+ puts "This happened while trying to configure Sparkly Authentication."
32
+ puts "You should verify that /config/initializers/sparkly_authentication.rb"
33
+ puts "is set up properly. It could be that you just haven't created the"
34
+ puts "model yet. If so, this error will disappear when the model exists."
35
+ puts
36
+ if ENV['AUTH_BACKTRACE']
37
+ puts $!.backtrace
38
+ else
39
+ puts "(Run with AUTH_BACKTRACE=true to see a full bactrace.)"
40
+ end
41
+ puts
42
+ end
43
+ end
44
+
45
+ # Useful for cleaning up after tests, but probably not much else.
46
+ def reset_configuration!
47
+ @configuration = Auth::Configuration.new
48
+ end
49
+
50
+ alias_method :kick!, :configure!
51
+ end
52
+ end
@@ -0,0 +1,64 @@
1
+ class Auth::Behavior::Base
2
+ class_inheritable_array :migrations
3
+ read_inheritable_attribute(:migrations) || write_inheritable_attribute(:migrations, [])
4
+
5
+ class << self
6
+ def apply_to_controllers
7
+ # Add additional methods to ApplicationController (or whatever controller Sparkly is told to use)
8
+ Auth.base_controller.send(:include, "#{name}::ControllerExtensions".constantize)
9
+ # why this doesn't work in cuke?
10
+ # if (container = self.class).const_defined?(:ControllerExtensions)
11
+ # Auth.base_controller.send(:include, container.const_get(:ControllerExtensions))
12
+ # end
13
+ #rescue NameError
14
+ end
15
+ end
16
+
17
+ def apply_to(model)
18
+ track_behavior(model.target) do
19
+ apply_to_accounts(model)
20
+ apply_to_passwords(Password)
21
+ end
22
+ end
23
+
24
+ def apply_to_passwords(password_model)
25
+ raise NotImplementedError, "Be sure to override #apply_to_passwords(passwords_model) in your Auth Behavior"
26
+ end
27
+
28
+ def apply_to_accounts(model_config)
29
+ raise NotImplementedError, "Be sure to override #apply_to_accounts(model_config) in your Auth Behavior"
30
+ end
31
+
32
+ private
33
+ def track_behavior(model)
34
+ if !behavior_tracked?(model)
35
+ track_behavior!(model)
36
+ yield
37
+ end
38
+ end
39
+
40
+ def behavior_tracked?(model)
41
+ behavior_tracker(model).include? behavior_name
42
+ end
43
+
44
+ def track_behavior!(model)
45
+ behavior_tracker(model) << behavior_name
46
+ end
47
+
48
+ def behavior_tracker(model)
49
+ model.instance_variable_get("@__behavior_tracker") || model.instance_variable_set("@__behavior_tracker", [])
50
+ end
51
+
52
+ def behavior_name
53
+ self.class.name
54
+ end
55
+
56
+ public
57
+ class << self
58
+ # Declares a migration template for a behavior. If sourcedir is given, it will be used as the location
59
+ # in which to find the template.
60
+ def migration(filename)
61
+ migrations << filename unless migrations.include?(filename)
62
+ end
63
+ end
64
+ end