sorcery 0.1.4 → 0.3.0

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 (111) hide show
  1. data/Gemfile +4 -2
  2. data/Gemfile.lock +16 -13
  3. data/README.rdoc +111 -78
  4. data/Rakefile +5 -0
  5. data/VERSION +1 -1
  6. data/lib/generators/sorcery_migration/sorcery_migration_generator.rb +24 -0
  7. data/lib/generators/sorcery_migration/templates/activity_logging.rb +17 -0
  8. data/lib/generators/sorcery_migration/templates/brute_force_protection.rb +11 -0
  9. data/lib/generators/sorcery_migration/templates/core.rb +16 -0
  10. data/lib/generators/sorcery_migration/templates/oauth.rb +14 -0
  11. data/lib/generators/sorcery_migration/templates/remember_me.rb +15 -0
  12. data/lib/generators/sorcery_migration/templates/reset_password.rb +13 -0
  13. data/lib/generators/sorcery_migration/templates/user_activation.rb +17 -0
  14. data/lib/sorcery/controller/adapters/sinatra.rb +97 -0
  15. data/lib/sorcery/controller/submodules/activity_logging.rb +20 -7
  16. data/lib/sorcery/controller/submodules/brute_force_protection.rb +9 -2
  17. data/lib/sorcery/controller/submodules/http_basic_auth.rb +18 -9
  18. data/lib/sorcery/controller/submodules/oauth/oauth1.rb +33 -0
  19. data/lib/sorcery/controller/submodules/oauth/oauth2.rb +24 -0
  20. data/lib/sorcery/controller/submodules/oauth/providers/facebook.rb +64 -0
  21. data/lib/sorcery/controller/submodules/oauth/providers/twitter.rb +61 -0
  22. data/lib/sorcery/controller/submodules/oauth.rb +95 -0
  23. data/lib/sorcery/controller/submodules/remember_me.rb +14 -5
  24. data/lib/sorcery/controller/submodules/session_timeout.rb +6 -1
  25. data/lib/sorcery/controller.rb +29 -17
  26. data/lib/sorcery/engine.rb +9 -2
  27. data/lib/sorcery/model/submodules/activity_logging.rb +13 -8
  28. data/lib/sorcery/model/submodules/brute_force_protection.rb +11 -8
  29. data/lib/sorcery/model/submodules/oauth.rb +53 -0
  30. data/lib/sorcery/model/submodules/remember_me.rb +5 -3
  31. data/lib/sorcery/model/submodules/reset_password.rb +16 -13
  32. data/lib/sorcery/model/submodules/user_activation.rb +38 -19
  33. data/lib/sorcery/model/temporary_token.rb +22 -0
  34. data/lib/sorcery/model.rb +10 -3
  35. data/lib/sorcery/sinatra.rb +14 -0
  36. data/lib/sorcery/test_helpers/rails.rb +57 -0
  37. data/lib/sorcery/test_helpers/sinatra.rb +131 -0
  38. data/lib/sorcery/test_helpers.rb +40 -0
  39. data/lib/sorcery.rb +20 -0
  40. data/sorcery.gemspec +144 -41
  41. data/spec/Gemfile +3 -2
  42. data/spec/Gemfile.lock +15 -2
  43. data/spec/rails3/app_root/.rspec +1 -0
  44. data/spec/rails3/{Gemfile → app_root/Gemfile} +4 -4
  45. data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +23 -3
  46. data/spec/rails3/app_root/app/controllers/application_controller.rb +42 -1
  47. data/spec/rails3/app_root/app/models/authentication.rb +3 -0
  48. data/spec/rails3/app_root/app/models/user.rb +4 -1
  49. data/spec/rails3/app_root/config/application.rb +1 -3
  50. data/spec/rails3/app_root/config/routes.rb +1 -10
  51. data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
  52. data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
  53. data/spec/rails3/app_root/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
  54. data/spec/rails3/{controller_activity_logging_spec.rb → app_root/spec/controller_activity_logging_spec.rb} +13 -13
  55. data/spec/rails3/{controller_brute_force_protection_spec.rb → app_root/spec/controller_brute_force_protection_spec.rb} +16 -6
  56. data/spec/rails3/{controller_http_basic_auth_spec.rb → app_root/spec/controller_http_basic_auth_spec.rb} +3 -3
  57. data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +119 -0
  58. data/spec/rails3/app_root/spec/controller_oauth_spec.rb +122 -0
  59. data/spec/rails3/{controller_remember_me_spec.rb → app_root/spec/controller_remember_me_spec.rb} +4 -4
  60. data/spec/rails3/{controller_session_timeout_spec.rb → app_root/spec/controller_session_timeout_spec.rb} +6 -6
  61. data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +20 -13
  62. data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
  63. data/spec/rails3/app_root/spec/spec_helper.rb +62 -0
  64. data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +60 -20
  65. data/spec/rails3/{user_activity_logging_spec.rb → app_root/spec/user_activity_logging_spec.rb} +4 -4
  66. data/spec/rails3/{user_brute_force_protection_spec.rb → app_root/spec/user_brute_force_protection_spec.rb} +7 -7
  67. data/spec/rails3/app_root/spec/user_oauth_spec.rb +39 -0
  68. data/spec/rails3/{user_remember_me_spec.rb → app_root/spec/user_remember_me_spec.rb} +4 -4
  69. data/spec/rails3/{user_reset_password_spec.rb → app_root/spec/user_reset_password_spec.rb} +21 -41
  70. data/spec/rails3/app_root/spec/user_spec.rb +317 -0
  71. data/spec/sinatra/Gemfile +12 -0
  72. data/spec/sinatra/Gemfile.lock +134 -0
  73. data/spec/sinatra/Rakefile +10 -0
  74. data/spec/sinatra/authentication.rb +3 -0
  75. data/spec/sinatra/db/migrate/activation/20101224223622_add_activation_to_users.rb +17 -0
  76. data/spec/sinatra/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +17 -0
  77. data/spec/sinatra/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +11 -0
  78. data/spec/sinatra/db/migrate/core/20101224223620_create_users.rb +16 -0
  79. data/spec/sinatra/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
  80. data/spec/sinatra/db/migrate/remember_me/20101224223623_add_remember_me_token_to_users.rb +15 -0
  81. data/spec/sinatra/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
  82. data/spec/sinatra/filters.rb +21 -0
  83. data/spec/sinatra/myapp.rb +133 -0
  84. data/spec/sinatra/sorcery_mailer.rb +25 -0
  85. data/spec/sinatra/spec/controller_activity_logging_spec.rb +85 -0
  86. data/spec/sinatra/spec/controller_brute_force_protection_spec.rb +69 -0
  87. data/spec/sinatra/spec/controller_http_basic_auth_spec.rb +53 -0
  88. data/spec/sinatra/spec/controller_oauth2_spec.rb +119 -0
  89. data/spec/sinatra/spec/controller_oauth_spec.rb +121 -0
  90. data/spec/sinatra/spec/controller_remember_me_spec.rb +64 -0
  91. data/spec/sinatra/spec/controller_session_timeout_spec.rb +52 -0
  92. data/spec/sinatra/spec/controller_spec.rb +120 -0
  93. data/spec/sinatra/spec/spec.opts +4 -0
  94. data/spec/sinatra/spec/spec_helper.rb +44 -0
  95. data/spec/sinatra/spec/user_activation_spec.rb +188 -0
  96. data/spec/sinatra/spec/user_activity_logging_spec.rb +36 -0
  97. data/spec/sinatra/spec/user_brute_force_protection_spec.rb +76 -0
  98. data/spec/sinatra/spec/user_oauth_spec.rb +39 -0
  99. data/spec/sinatra/spec/user_remember_me_spec.rb +66 -0
  100. data/spec/sinatra/spec/user_reset_password_spec.rb +178 -0
  101. data/spec/{rails3 → sinatra/spec}/user_spec.rb +68 -38
  102. data/spec/sinatra/user.rb +6 -0
  103. data/spec/sinatra/views/test_login.erb +4 -0
  104. data/spec/untitled folder +18 -0
  105. metadata +201 -58
  106. data/spec/rails3/app_root/test/fixtures/users.yml +0 -9
  107. data/spec/rails3/app_root/test/performance/browsing_test.rb +0 -9
  108. data/spec/rails3/app_root/test/test_helper.rb +0 -13
  109. data/spec/rails3/app_root/test/unit/user_test.rb +0 -8
  110. data/spec/rails3/spec_helper.rb +0 -135
  111. /data/spec/rails3/{Rakefile → app_root/Rakefile} +0 -0
@@ -1,27 +1,25 @@
1
1
  module Sorcery
2
2
  module Model
3
3
  module Submodules
4
+ # This module helps protect user accounts by locking them down after too many failed attemps to login were detected.
5
+ # This is the model part of the submodule which provides configuration options and methods for locking and unlocking the user.
4
6
  module BruteForceProtection
5
7
  def self.included(base)
6
8
  base.sorcery_config.class_eval do
7
9
  attr_accessor :failed_logins_count_attribute_name, # failed logins attribute name.
8
10
  :lock_expires_at_attribute_name, # this field indicates whether user is banned and when it will be active again.
9
- :consecutive_login_retries_amount_allowed, # how many failed logins allowed.
11
+ :consecutive_login_retries_amount_limit, # how many failed logins allowed.
10
12
  :login_lock_time_period # how long the user should be banned. in seconds. 0 for permanent.
11
13
  end
12
14
 
13
15
  base.sorcery_config.instance_eval do
14
16
  @defaults.merge!(:@failed_logins_count_attribute_name => :failed_logins_count,
15
17
  :@lock_expires_at_attribute_name => :lock_expires_at,
16
- :@consecutive_login_retries_amount_allowed => 50,
18
+ :@consecutive_login_retries_amount_limit => 50,
17
19
  :@login_lock_time_period => 3600)
18
20
  reset!
19
21
  end
20
22
 
21
- base.class_eval do
22
-
23
- end
24
-
25
23
  base.sorcery_config.before_authenticate << :prevent_locked_user_login
26
24
  base.extend(ClassMethods)
27
25
  base.send(:include, InstanceMethods)
@@ -33,11 +31,14 @@ module Sorcery
33
31
  end
34
32
 
35
33
  module InstanceMethods
34
+ # Called by the controller to increment the failed logins counter.
35
+ # Calls 'lock!' if login retries limit was reached.
36
36
  def register_failed_login!
37
37
  config = sorcery_config
38
+ return if !unlocked?
38
39
  self.increment(config.failed_logins_count_attribute_name)
39
40
  save!
40
- self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_allowed
41
+ self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_limit
41
42
  end
42
43
 
43
44
  protected
@@ -58,9 +59,11 @@ module Sorcery
58
59
  self.send(config.lock_expires_at_attribute_name).nil?
59
60
  end
60
61
 
62
+ # Prevents a locked user from logging in, and unlocks users that expired their lock time.
63
+ # Runs as a hook before authenticate.
61
64
  def prevent_locked_user_login
62
65
  config = sorcery_config
63
- if !self.unlocked?
66
+ if !self.unlocked? && config.login_lock_time_period != 0
64
67
  self.unlock! if self.send(config.lock_expires_at_attribute_name) <= Time.now.utc
65
68
  end
66
69
  unlocked?
@@ -0,0 +1,53 @@
1
+ module Sorcery
2
+ module Model
3
+ module Submodules
4
+ # This submodule helps you login users from OAuth providers such as Twitter.
5
+ # This is the model part which handles finding the user using access tokens.
6
+ # For the controller options see Sorcery::Controller::Oauth.
7
+ #
8
+ # Socery assumes (read: requires) you will create external users in the same table where you keep your regular users,
9
+ # but that you will have a separate table for keeping their external authentication data,
10
+ # and that that separate table has a few rows for each user, facebook and twitter for example (a one-to-many relationship).
11
+ #
12
+ # External users will have a null crypted_password field, since we do not hold their password.
13
+ # They will not be sent activation emails on creation.
14
+ module Oauth
15
+ def self.included(base)
16
+ base.sorcery_config.class_eval do
17
+ attr_accessor :authentications_class,
18
+ :authentications_user_id_attribute_name,
19
+ :provider_attribute_name,
20
+ :provider_uid_attribute_name
21
+
22
+ end
23
+
24
+ base.sorcery_config.instance_eval do
25
+ @defaults.merge!(:@authentications_class => nil,
26
+ :@authentications_user_id_attribute_name => :user_id,
27
+ :@provider_attribute_name => :provider,
28
+ :@provider_uid_attribute_name => :uid)
29
+
30
+ reset!
31
+ end
32
+
33
+ base.send(:include, InstanceMethods)
34
+ base.extend(ClassMethods)
35
+ end
36
+
37
+ module ClassMethods
38
+ # takes a provider and uid and finds a user by them.
39
+ def load_from_provider(provider,uid)
40
+ config = sorcery_config
41
+ authentication = config.authentications_class.find_by_provider_and_uid(provider, uid)
42
+ user = find(authentication.send(config.authentications_user_id_attribute_name)) if authentication
43
+ end
44
+ end
45
+
46
+ module InstanceMethods
47
+
48
+ end
49
+
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,6 +1,8 @@
1
1
  module Sorcery
2
2
  module Model
3
3
  module Submodules
4
+ # The Remember Me submodule takes care of setting the user's cookie so that he will be automatically logged in to the site on every visit,
5
+ # until the cookie expires.
4
6
  module RememberMe
5
7
  def self.included(base)
6
8
  base.sorcery_config.class_eval do
@@ -22,15 +24,15 @@ module Sorcery
22
24
  end
23
25
 
24
26
  module InstanceMethods
25
- # You shouldn't really use this one - it's called by the controller's 'remember_me!' method.
27
+ # You shouldn't really use this one yourself - it's called by the controller's 'remember_me!' method.
26
28
  def remember_me!
27
29
  config = sorcery_config
28
- self.send(:"#{config.remember_me_token_attribute_name}=", generate_random_code)
30
+ self.send(:"#{config.remember_me_token_attribute_name}=", generate_random_token)
29
31
  self.send(:"#{config.remember_me_token_expires_at_attribute_name}=", Time.now + config.remember_me_for)
30
32
  self.save!(:validate => false)
31
33
  end
32
34
 
33
- # You shouldn't really use this one - it's called by the controller's 'forget_me!' method.
35
+ # You shouldn't really use this one yourself - it's called by the controller's 'forget_me!' method.
34
36
  def forget_me!
35
37
  config = sorcery_config
36
38
  self.send(:"#{config.remember_me_token_attribute_name}=", nil)
@@ -2,6 +2,12 @@ module Sorcery
2
2
  module Model
3
3
  module Submodules
4
4
  # This submodule adds the ability to reset password via email confirmation.
5
+ # When the user requests an email is sent to him with a url.
6
+ # The url includes a token, which is also saved with the user's record in the db.
7
+ # The token has configurable expiration.
8
+ # When the user clicks the url in the email, providing the token has not yet expired, he will be able to reset his password via a form.
9
+ #
10
+ # When using this submodule, supplying a mailer is mandatory.
5
11
  module ResetPassword
6
12
  def self.included(base)
7
13
  base.sorcery_config.class_eval do
@@ -30,22 +36,22 @@ module Sorcery
30
36
  base.sorcery_config.after_config << :validate_mailer_defined
31
37
 
32
38
  base.extend(ClassMethods)
39
+ base.send(:include, TemporaryToken)
33
40
  base.send(:include, InstanceMethods)
34
41
  end
35
42
 
36
43
  module ClassMethods
37
-
44
+ # Find user by token, also checks for expiration.
45
+ # Returns the user if token found and is valid.
38
46
  def load_from_reset_password_token(token)
39
- return nil if token.blank?
40
- user = where("#{@sorcery_config.reset_password_token_attribute_name} = ?", token).first
41
- if !user.blank? && !@sorcery_config.reset_password_expiration_period.nil?
42
- return user.reset_password_token_valid? ? user : nil
43
- end
44
- user
47
+ token_attr_name = @sorcery_config.reset_password_token_attribute_name
48
+ token_expiration_date_attr = @sorcery_config.reset_password_token_expires_at_attribute_name
49
+ load_from_token(token, token_attr_name, token_expiration_date_attr)
45
50
  end
46
51
 
47
52
  protected
48
53
 
54
+ # This submodule requires the developer to define his own mailer class to be used by it.
49
55
  def validate_mailer_defined
50
56
  msg = "To use reset_password submodule, you must define a mailer (config.reset_password_mailer = YourMailerClass)."
51
57
  raise ArgumentError, msg if @sorcery_config.reset_password_mailer == nil
@@ -59,7 +65,7 @@ module Sorcery
59
65
  config = sorcery_config
60
66
  # hammering protection
61
67
  return if config.reset_password_time_between_emails && self.send(config.reset_password_email_sent_at_attribute_name) && self.send(config.reset_password_email_sent_at_attribute_name) > config.reset_password_time_between_emails.ago.utc
62
- self.send(:"#{config.reset_password_token_attribute_name}=", generate_random_code)
68
+ self.send(:"#{config.reset_password_token_attribute_name}=", generate_random_token)
63
69
  self.send(:"#{config.reset_password_token_expires_at_attribute_name}=", Time.now.utc + config.reset_password_expiration_period) if config.reset_password_expiration_period
64
70
  self.send(:"#{config.reset_password_email_sent_at_attribute_name}=", Time.now.utc)
65
71
  self.class.transaction do
@@ -68,18 +74,15 @@ module Sorcery
68
74
  end
69
75
  end
70
76
 
77
+ # Clears token and tries to update the new password for the user.
71
78
  def reset_password!(params)
72
79
  clear_reset_password_token
73
80
  update_attributes(params)
74
81
  end
75
-
76
- def reset_password_token_valid?
77
- config = sorcery_config
78
- config.reset_password_expiration_period ? Time.now.utc < self.send(config.reset_password_token_expires_at_attribute_name) : true
79
- end
80
82
 
81
83
  protected
82
84
 
85
+ # Clears the token.
83
86
  def clear_reset_password_token
84
87
  config = sorcery_config
85
88
  self.send(:"#{config.reset_password_token_attribute_name}=", nil)
@@ -8,27 +8,33 @@ module Sorcery
8
8
  module UserActivation
9
9
  def self.included(base)
10
10
  base.sorcery_config.class_eval do
11
- attr_accessor :activation_state_attribute_name, # the attribute name to hold activation state (active/pending).
12
- :activation_code_attribute_name, # the attribute name to hold activation code (sent by email).
13
- :user_activation_mailer, # your mailer class. Needed.
14
- :activation_needed_email_method_name, # activation needed email method on your mailer class.
15
- :activation_success_email_method_name, # activation success email method on your mailer class.
16
- :prevent_non_active_users_to_login # do you want to prevent or allow users that did not activate by email to login?
11
+ attr_accessor :activation_state_attribute_name, # the attribute name to hold activation state (active/pending).
12
+ :activation_token_attribute_name, # the attribute name to hold activation code (sent by email).
13
+ :activation_token_expires_at_attribute_name, # the attribute name to hold activation code expiration date.
14
+ :activation_token_expiration_period, # how many seconds before the activation code expires. nil for never expires.
15
+ :user_activation_mailer, # your mailer class. Required.
16
+ :activation_needed_email_method_name, # activation needed email method on your mailer class.
17
+ :activation_success_email_method_name, # activation success email method on your mailer class.
18
+ :prevent_non_active_users_to_login # do you want to prevent or allow users that did not activate by email to login?
17
19
  end
18
20
 
19
21
  base.sorcery_config.instance_eval do
20
- @defaults.merge!(:@activation_state_attribute_name => :activation_state,
21
- :@activation_code_attribute_name => :activation_code,
22
- :@user_activation_mailer => nil,
23
- :@activation_needed_email_method_name => :activation_needed_email,
24
- :@activation_success_email_method_name => :activation_success_email,
25
- :@prevent_non_active_users_to_login => true)
22
+ @defaults.merge!(:@activation_state_attribute_name => :activation_state,
23
+ :@activation_token_attribute_name => :activation_token,
24
+ :@activation_token_expires_at_attribute_name => :activation_token_expires_at,
25
+ :@activation_token_expiration_period => nil,
26
+ :@user_activation_mailer => nil,
27
+ :@activation_needed_email_method_name => :activation_needed_email,
28
+ :@activation_success_email_method_name => :activation_success_email,
29
+ :@prevent_non_active_users_to_login => true)
26
30
  reset!
27
31
  end
28
32
 
29
33
  base.class_eval do
30
- before_create :setup_activation
31
- after_create :send_activation_needed_email!
34
+ # don't setup activation if no password supplied - this user is created automatically
35
+ before_create :setup_activation, :if => Proc.new { |user| user.send(sorcery_config.password_attribute_name).present? }
36
+ # don't send activation needed email if no crypted password created - this user is external (OAuth etc.)
37
+ after_create :send_activation_needed_email!, :if => Proc.new { |user| !user.external?}
32
38
  end
33
39
 
34
40
  base.sorcery_config.after_config << :validate_mailer_defined
@@ -36,12 +42,22 @@ module Sorcery
36
42
  base.sorcery_config.before_authenticate << :prevent_non_active_login
37
43
 
38
44
  base.extend(ClassMethods)
45
+ base.send(:include, TemporaryToken)
39
46
  base.send(:include, InstanceMethods)
40
47
  end
41
48
 
42
49
  module ClassMethods
50
+ # Find user by token, also checks for expiration.
51
+ # Returns the user if token found and is valid.
52
+ def load_from_activation_token(token)
53
+ token_attr_name = @sorcery_config.activation_token_attribute_name
54
+ token_expiration_date_attr = @sorcery_config.activation_token_expires_at_attribute_name
55
+ load_from_token(token, token_attr_name, token_expiration_date_attr)
56
+ end
57
+
43
58
  protected
44
59
 
60
+ # This submodule requires the developer to define his own mailer class to be used by it.
45
61
  def validate_mailer_defined
46
62
  msg = "To use user_activation submodule, you must define a mailer (config.user_activation_mailer = YourMailerClass)."
47
63
  raise ArgumentError, msg if @sorcery_config.user_activation_mailer == nil
@@ -49,23 +65,26 @@ module Sorcery
49
65
  end
50
66
 
51
67
  module InstanceMethods
68
+ # clears activation code, sets the user as 'active' and optionaly sends a success email.
52
69
  def activate!
53
70
  config = sorcery_config
54
- self.send(:"#{config.activation_code_attribute_name}=", nil)
71
+ self.send(:"#{config.activation_token_attribute_name}=", nil)
55
72
  self.send(:"#{config.activation_state_attribute_name}=", "active")
56
- send_activation_success_email!
73
+ send_activation_success_email! unless self.external?
57
74
  save!(:validate => false) # don't run validations
58
75
  end
59
-
76
+
60
77
  protected
61
78
 
62
79
  def setup_activation
63
80
  config = sorcery_config
64
- generated_activation_code = CryptoProviders::SHA1.encrypt( Time.now.to_s.split(//).sort_by {rand}.join )
65
- self.send(:"#{config.activation_code_attribute_name}=", generated_activation_code)
81
+ generated_activation_token = generate_random_token
82
+ self.send(:"#{config.activation_token_attribute_name}=", generated_activation_token)
66
83
  self.send(:"#{config.activation_state_attribute_name}=", "pending")
84
+ self.send(:"#{config.activation_token_expires_at_attribute_name}=", Time.now.utc + config.activation_token_expiration_period) if config.activation_token_expiration_period
67
85
  end
68
86
 
87
+ # called automatically after user initial creation.
69
88
  def send_activation_needed_email!
70
89
  generic_send_email(:activation_needed_email_method_name, :user_activation_mailer) unless sorcery_config.activation_needed_email_method_name.nil?
71
90
  end
@@ -0,0 +1,22 @@
1
+ module Sorcery
2
+ module Model
3
+ # This module encapsulates the logic for temporary token.
4
+ # A temporary token is created to identify a user in scenarios such as reseting password and activating the user by email.
5
+ module TemporaryToken
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ def load_from_token(token, token_attr_name, token_expiration_date_attr)
12
+ return nil if token.blank?
13
+ user = where("#{token_attr_name} = ?", token).first
14
+ if !user.blank? && !user.send(token_expiration_date_attr).nil?
15
+ return Time.now.utc < user.send(token_expiration_date_attr) ? user : nil
16
+ end
17
+ user
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/sorcery/model.rb CHANGED
@@ -55,7 +55,8 @@ module Sorcery
55
55
  _salt = user.send(@sorcery_config.salt_attribute_name) if user && !@sorcery_config.salt_attribute_name.nil? && !@sorcery_config.encryption_provider.nil?
56
56
  user if user && @sorcery_config.before_authenticate.all? {|c| user.send(c)} && credentials_match?(user.send(@sorcery_config.crypted_password_attribute_name),credentials[1],_salt)
57
57
  end
58
-
58
+
59
+ # Calls the configured encryption provider to compare the supplied password with the encrypted one.
59
60
  def credentials_match?(crypted, *tokens)
60
61
  return crypted == tokens.join if @sorcery_config.encryption_provider.nil?
61
62
  @sorcery_config.encryption_provider.matches?(crypted, *tokens)
@@ -78,13 +79,19 @@ module Sorcery
78
79
  self.class.sorcery_config
79
80
  end
80
81
 
82
+ # identifies whether this user is regular, i.e. we hold his credentials in our db,
83
+ # or that he is external, and his credentials are saved elsewhere (twitter/facebook etc.).
84
+ def external?
85
+ send(sorcery_config.crypted_password_attribute_name).nil?
86
+ end
87
+
81
88
  protected
82
89
 
83
90
  # creates new salt and saves it.
84
91
  # encrypts password with salt and save it.
85
92
  def encrypt_password
86
93
  config = sorcery_config
87
- new_salt = self.send(:"#{config.salt_attribute_name}=", generate_random_code) if !config.salt_attribute_name.nil?
94
+ new_salt = self.send(:"#{config.salt_attribute_name}=", generate_random_token) if !config.salt_attribute_name.nil?
88
95
  self.send(:"#{config.crypted_password_attribute_name}=", self.class.encrypt(self.send(config.password_attribute_name),new_salt))
89
96
  end
90
97
 
@@ -104,7 +111,7 @@ module Sorcery
104
111
  end
105
112
 
106
113
  # Random code, used for salt and temporary tokens.
107
- def generate_random_code
114
+ def generate_random_token
108
115
  return Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
109
116
  end
110
117
  end
@@ -0,0 +1,14 @@
1
+ ActiveRecord::Base.send(:include, Sorcery::Model) if defined?(ActiveRecord)
2
+ if defined?(Sinatra::Base)
3
+ Sinatra::Base.send(:include, Sorcery::Controller::Adapters::Sinatra)
4
+ Sinatra::Base.send(:include, Sorcery::Controller)
5
+ # Sorcery::Controller::Config.class_eval do
6
+ # class << self
7
+ # def submodules=(mods)
8
+ # @submodules = mods
9
+ # Sinatra::Base.send(:include, Sorcery::Controller::Adapters::Sinatra)
10
+ # Sinatra::Base.send(:include, Sorcery::Controller)
11
+ # end
12
+ # end
13
+ # end
14
+ end
@@ -0,0 +1,57 @@
1
+ module Sorcery
2
+ module TestHelpers
3
+ module Rails
4
+ SUBMODUELS_AUTO_ADDED_CONTROLLER_FILTERS = [:register_last_activity_time_to_db, :deny_banned_user, :validate_session]
5
+
6
+ def sorcery_reload!(submodules = [], options = {})
7
+ reload_user_class
8
+
9
+ # return to no-module configuration
10
+ ::Sorcery::Controller::Config.init!
11
+ ::Sorcery::Controller::Config.reset!
12
+
13
+ # remove all plugin before_filters so they won't fail other tests.
14
+ # I don't like this way, but I didn't find another.
15
+ # hopefully it won't break until Rails 4.
16
+ ApplicationController._process_action_callbacks.delete_if {|c| SUBMODUELS_AUTO_ADDED_CONTROLLER_FILTERS.include?(c.filter) }
17
+
18
+ # configure
19
+ ::Sorcery::Controller::Config.submodules = submodules
20
+ ::Sorcery::Controller::Config.user_class = nil
21
+ ActionController::Base.send(:include,::Sorcery::Controller)
22
+
23
+ User.activate_sorcery! do |config|
24
+ options.each do |property,value|
25
+ config.send(:"#{property}=", value)
26
+ end
27
+ end
28
+ end
29
+
30
+ def sorcery_controller_property_set(property, value)
31
+ ApplicationController.activate_sorcery! do |config|
32
+ config.send(:"#{property}=", value)
33
+ end
34
+ end
35
+
36
+ def sorcery_controller_oauth_property_set(provider, property, value)
37
+ ApplicationController.activate_sorcery! do |config|
38
+ config.send(provider).send(:"#{property}=", value)
39
+ end
40
+ end
41
+
42
+ def login_user(user = nil)
43
+ user ||= @user
44
+ subject.send(:login_user,user)
45
+ subject.send(:after_login!,user,[user.username,'secret'])
46
+ end
47
+
48
+ def logout_user
49
+ subject.send(:logout)
50
+ end
51
+
52
+ def clear_user_without_logout
53
+ subject.instance_variable_set(:@current_user,nil)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,131 @@
1
+ module Sorcery
2
+ module TestHelpers
3
+ module Sinatra
4
+
5
+ class MyApp
6
+ class << self
7
+ def new
8
+ return this
9
+ end
10
+ end
11
+ end
12
+
13
+ class ::Sinatra::Application
14
+ class << self
15
+ attr_accessor :sorcery_vars
16
+ end
17
+ @sorcery_vars = {}
18
+
19
+ # NOTE: see before and after test filters in filters.rb
20
+
21
+ def save_instance_vars
22
+ instance_variables.each do |var|
23
+ self.class.sorcery_vars[:"#{var.to_s.delete("@")}"] = instance_variable_get(var)
24
+ end
25
+ end
26
+ end
27
+
28
+ ::RSpec::Matchers.define :redirect_to do |expected|
29
+ match do |actual|
30
+ actual.status == 302 && actual.location == expected
31
+ end
32
+ end
33
+
34
+ def get_sinatra_app(app)
35
+ while app.class != ::Sinatra::Application do
36
+ app = app.instance_variable_get(:@app)
37
+ end
38
+ app
39
+ end
40
+
41
+ def login_user(user=nil)
42
+ user ||= @user
43
+ get_sinatra_app(subject).send(:login_user,user)
44
+ get_sinatra_app(subject).send(:after_login!,user,[user.username,'secret'])
45
+ end
46
+
47
+ def logout_user
48
+ get_sinatra_app(subject).send(:logout)
49
+ end
50
+
51
+ def clear_user_without_logout
52
+ get_sinatra_app(subject).instance_variable_set(:@current_user,nil)
53
+ end
54
+
55
+ def assigns
56
+ ::Sinatra::Application.sorcery_vars
57
+ end
58
+
59
+ class SessionData
60
+ def initialize(cookies)
61
+ @cookies = cookies
62
+ @data = cookies['rack.session']
63
+ if @data
64
+ @data = @data.unpack("m*").first
65
+ @data = Marshal.load(@data)
66
+ else
67
+ @data = {}
68
+ end
69
+ end
70
+
71
+ def [](key)
72
+ @data[key]
73
+ end
74
+
75
+ def []=(key, value)
76
+ @data[key] = value
77
+ session_data = Marshal.dump(@data)
78
+ session_data = [session_data].pack("m*")
79
+ @cookies.merge("rack.session=#{Rack::Utils.escape(session_data)}", URI.parse("//example.org//"))
80
+ raise "session variable not set" unless @cookies['rack.session'] == session_data
81
+ end
82
+ end
83
+
84
+ def session
85
+ SessionData.new(rack_test_session.instance_variable_get(:@rack_mock_session).cookie_jar)
86
+ end
87
+
88
+ def cookies
89
+ rack_test_session.instance_variable_get(:@rack_mock_session).cookie_jar
90
+ end
91
+
92
+ def sorcery_reload!(submodules = [], options = {})
93
+ reload_user_class
94
+
95
+ # return to no-module configuration
96
+ ::Sorcery::Controller::Config.init!
97
+ ::Sorcery::Controller::Config.reset!
98
+
99
+ # clear all filters
100
+ ::Sinatra::Application.instance_variable_set(:@filters,{:before => [],:after => []})
101
+ ::Sinatra::Application.class_eval do
102
+ load File.join(File.dirname(__FILE__),'..','..','..','spec','sinatra','filters.rb')
103
+ end
104
+
105
+ # configure
106
+ ::Sorcery::Controller::Config.submodules = submodules
107
+ ::Sorcery::Controller::Config.user_class = nil
108
+ ::Sinatra::Application.send(:include, Sorcery::Controller::Adapters::Sinatra)
109
+ ::Sinatra::Application.send(:include, Sorcery::Controller)
110
+
111
+ User.activate_sorcery! do |config|
112
+ options.each do |property,value|
113
+ config.send(:"#{property}=", value)
114
+ end
115
+ end
116
+ end
117
+
118
+ def sorcery_controller_property_set(property, value)
119
+ ::Sinatra::Application.activate_sorcery! do |config|
120
+ config.send(:"#{property}=", value)
121
+ end
122
+ end
123
+
124
+ def sorcery_controller_oauth_property_set(provider, property, value)
125
+ ::Sinatra::Application.activate_sorcery! do |config|
126
+ config.send(provider).send(:"#{property}=", value)
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,40 @@
1
+ module Sorcery
2
+ module TestHelpers
3
+ # a patch to fix a bug in testing that happens when you 'destroy' a session twice.
4
+ # After the first destroy, the session is an ordinary hash, and then when destroy is called again there's an exception.
5
+ class ::Hash
6
+ def destroy
7
+ clear
8
+ end
9
+ end
10
+
11
+ def create_new_user(attributes_hash = nil)
12
+ user_attributes_hash = attributes_hash || {:username => 'gizmo', :email => "bla@bla.com", :password => 'secret'}
13
+ @user = User.new(user_attributes_hash)
14
+ @user.save!
15
+ @user
16
+ end
17
+
18
+ def create_new_external_user(provider, attributes_hash = nil)
19
+ user_attributes_hash = attributes_hash || {:username => 'gizmo', :authentications_attributes => [{:provider => provider, :uid => 123}]}
20
+ @user = User.new(user_attributes_hash)
21
+ @user.save!
22
+ @user
23
+ end
24
+
25
+ def sorcery_model_property_set(property, *values)
26
+ User.class_eval do
27
+ sorcery_config.send(:"#{property}=", *values)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ # reload user class between specs
34
+ # so it will be possible to test the different submodules in isolation
35
+ def reload_user_class
36
+ Object.send(:remove_const,:User)
37
+ load 'user.rb'
38
+ end
39
+ end
40
+ end