sorcery 0.8.5 → 0.8.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sorcery might be problematic. Click here for more details.

Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +60 -4
  4. data/CHANGELOG.md +15 -1
  5. data/Gemfile +9 -18
  6. data/Gemfile.rails4 +8 -10
  7. data/README.md +31 -11
  8. data/VERSION +1 -1
  9. data/gemfiles/active_record-rails41.gemfile +6 -0
  10. data/gemfiles/mongo_mapper-rails41.gemfile +8 -0
  11. data/gemfiles/mongoid-rails41.gemfile +11 -0
  12. data/lib/sorcery.rb +20 -28
  13. data/lib/sorcery/controller.rb +6 -11
  14. data/lib/sorcery/controller/submodules/external.rb +30 -15
  15. data/lib/sorcery/controller/submodules/session_timeout.rb +1 -1
  16. data/lib/sorcery/model.rb +102 -70
  17. data/lib/sorcery/model/adapters/active_record.rb +7 -2
  18. data/lib/sorcery/model/adapters/datamapper.rb +123 -0
  19. data/lib/sorcery/model/adapters/mongo_mapper.rb +8 -4
  20. data/lib/sorcery/model/adapters/mongoid.rb +6 -6
  21. data/lib/sorcery/model/submodules/activity_logging.rb +24 -0
  22. data/lib/sorcery/model/submodules/brute_force_protection.rb +16 -0
  23. data/lib/sorcery/model/submodules/remember_me.rb +19 -4
  24. data/lib/sorcery/model/submodules/reset_password.rb +30 -13
  25. data/lib/sorcery/model/submodules/user_activation.rb +53 -22
  26. data/lib/sorcery/{controller/submodules/external/protocols → protocols}/certs/ca-bundle.crt +0 -0
  27. data/lib/sorcery/protocols/oauth.rb +42 -0
  28. data/lib/sorcery/protocols/oauth2.rb +47 -0
  29. data/lib/sorcery/providers/base.rb +27 -0
  30. data/lib/sorcery/providers/facebook.rb +63 -0
  31. data/lib/sorcery/providers/github.rb +51 -0
  32. data/lib/sorcery/providers/google.rb +51 -0
  33. data/lib/sorcery/providers/linkedin.rb +66 -0
  34. data/lib/sorcery/providers/liveid.rb +53 -0
  35. data/lib/sorcery/providers/twitter.rb +59 -0
  36. data/lib/sorcery/providers/vk.rb +61 -0
  37. data/lib/sorcery/providers/xing.rb +64 -0
  38. data/lib/sorcery/test_helpers/internal.rb +3 -3
  39. data/lib/sorcery/test_helpers/internal/rails.rb +14 -3
  40. data/lib/sorcery/test_helpers/rails.rb +1 -10
  41. data/lib/sorcery/test_helpers/rails/controller.rb +17 -0
  42. data/lib/sorcery/test_helpers/rails/integration.rb +26 -0
  43. data/sorcery.gemspec +14 -18
  44. data/spec/active_record/controller_activity_logging_spec.rb +5 -116
  45. data/spec/active_record/controller_brute_force_protection_spec.rb +69 -47
  46. data/spec/active_record/controller_http_basic_auth_spec.rb +24 -18
  47. data/spec/active_record/controller_oauth2_spec.rb +112 -187
  48. data/spec/active_record/controller_oauth_spec.rb +41 -37
  49. data/spec/active_record/controller_remember_me_spec.rb +39 -38
  50. data/spec/active_record/controller_session_timeout_spec.rb +31 -16
  51. data/spec/active_record/controller_spec.rb +4 -178
  52. data/spec/active_record/integration_spec.rb +1 -1
  53. data/spec/active_record/user_activation_spec.rb +1 -1
  54. data/spec/active_record/user_activity_logging_spec.rb +1 -1
  55. data/spec/active_record/user_brute_force_protection_spec.rb +1 -1
  56. data/spec/active_record/user_oauth_spec.rb +1 -1
  57. data/spec/active_record/user_remember_me_spec.rb +1 -1
  58. data/spec/active_record/user_reset_password_spec.rb +1 -1
  59. data/spec/active_record/user_spec.rb +7 -8
  60. data/spec/datamapper/controller_activity_logging_spec.rb +17 -0
  61. data/spec/datamapper/controller_spec.rb +8 -0
  62. data/spec/datamapper/user_activation_spec.rb +10 -0
  63. data/spec/datamapper/user_activity_logging_spec.rb +9 -0
  64. data/spec/datamapper/user_brute_force_protection_spec.rb +9 -0
  65. data/spec/datamapper/user_oauth_spec.rb +9 -0
  66. data/spec/datamapper/user_remember_me_spec.rb +8 -0
  67. data/spec/datamapper/user_reset_password_spec.rb +8 -0
  68. data/spec/datamapper/user_spec.rb +27 -0
  69. data/spec/mongo_mapper/controller_spec.rb +4 -171
  70. data/spec/mongo_mapper/user_activation_spec.rb +1 -2
  71. data/spec/mongo_mapper/user_activity_logging_spec.rb +1 -1
  72. data/spec/mongo_mapper/user_brute_force_protection_spec.rb +1 -1
  73. data/spec/mongo_mapper/user_oauth_spec.rb +1 -1
  74. data/spec/mongo_mapper/user_remember_me_spec.rb +1 -1
  75. data/spec/mongo_mapper/user_reset_password_spec.rb +1 -1
  76. data/spec/mongo_mapper/user_spec.rb +7 -8
  77. data/spec/mongoid/controller_activity_logging_spec.rb +4 -99
  78. data/spec/mongoid/controller_spec.rb +4 -182
  79. data/spec/mongoid/user_activation_spec.rb +1 -2
  80. data/spec/mongoid/user_activity_logging_spec.rb +1 -2
  81. data/spec/mongoid/user_brute_force_protection_spec.rb +1 -2
  82. data/spec/mongoid/user_oauth_spec.rb +1 -2
  83. data/spec/mongoid/user_remember_me_spec.rb +1 -2
  84. data/spec/mongoid/user_reset_password_spec.rb +1 -2
  85. data/spec/mongoid/user_spec.rb +8 -9
  86. data/spec/orm/active_record.rb +2 -0
  87. data/spec/orm/datamapper.rb +34 -0
  88. data/spec/orm/mongo_mapper.rb +1 -0
  89. data/spec/orm/mongoid.rb +1 -0
  90. data/spec/rails_app/app/controllers/sorcery_controller.rb +64 -59
  91. data/spec/rails_app/app/datamapper/authentication.rb +8 -0
  92. data/spec/rails_app/app/datamapper/user.rb +7 -0
  93. data/spec/rails_app/config/routes.rb +18 -13
  94. data/spec/shared_examples/controller_activity_logging_shared_examples.rb +125 -0
  95. data/spec/shared_examples/controller_oauth2_shared_examples.rb +32 -36
  96. data/spec/shared_examples/controller_oauth_shared_examples.rb +19 -26
  97. data/spec/shared_examples/controller_shared_examples.rb +203 -0
  98. data/spec/shared_examples/user_activation_shared_examples.rb +107 -90
  99. data/spec/shared_examples/user_activity_logging_shared_examples.rb +10 -10
  100. data/spec/shared_examples/user_brute_force_protection_shared_examples.rb +14 -13
  101. data/spec/shared_examples/user_oauth_shared_examples.rb +23 -15
  102. data/spec/shared_examples/user_remember_me_shared_examples.rb +32 -23
  103. data/spec/shared_examples/user_reset_password_shared_examples.rb +136 -115
  104. data/spec/shared_examples/user_shared_examples.rb +206 -146
  105. data/spec/sorcery_crypto_providers_spec.rb +28 -28
  106. data/spec/spec_helper.rb +15 -6
  107. metadata +83 -127
  108. data/lib/sorcery/controller/submodules/external/protocols/oauth1.rb +0 -46
  109. data/lib/sorcery/controller/submodules/external/protocols/oauth2.rb +0 -50
  110. data/lib/sorcery/controller/submodules/external/providers/base.rb +0 -21
  111. data/lib/sorcery/controller/submodules/external/providers/facebook.rb +0 -99
  112. data/lib/sorcery/controller/submodules/external/providers/github.rb +0 -93
  113. data/lib/sorcery/controller/submodules/external/providers/google.rb +0 -92
  114. data/lib/sorcery/controller/submodules/external/providers/linkedin.rb +0 -103
  115. data/lib/sorcery/controller/submodules/external/providers/liveid.rb +0 -93
  116. data/lib/sorcery/controller/submodules/external/providers/twitter.rb +0 -94
  117. data/lib/sorcery/controller/submodules/external/providers/vk.rb +0 -101
  118. data/lib/sorcery/controller/submodules/external/providers/xing.rb +0 -98
  119. data/lib/sorcery/test_helpers.rb +0 -5
@@ -37,7 +37,7 @@ module Sorcery
37
37
  # To be used as a before_filter, before require_login
38
38
  def validate_session
39
39
  session_to_use = Config.session_timeout_from_last_action ? session[:last_action_time] : session[:login_time]
40
- if session_to_use && (Time.now.in_time_zone - session_to_use > Config.session_timeout)
40
+ if session_to_use && (Time.now.in_time_zone - session_to_use.to_time > Config.session_timeout)
41
41
  reset_sorcery_session
42
42
  @current_user = nil
43
43
  else
@@ -7,86 +7,114 @@ module Sorcery
7
7
  # which when called adds the other capabilities to the class.
8
8
  # This method is also the place to configure the plugin in the Model layer.
9
9
  module Model
10
- def self.included(klass)
11
- klass.class_eval do
12
- class << self
13
- def authenticates_with_sorcery!
14
- @sorcery_config = Config.new
15
- self.class_eval do
16
- extend ClassMethods # included here, before submodules, so they can be overriden by them.
17
- include InstanceMethods
18
- include TemporaryToken
19
- end
10
+ def authenticates_with_sorcery!
11
+ @sorcery_config = Config.new
20
12
 
21
- include_required_submodules!
13
+ extend ClassMethods # included here, before submodules, so they can be overriden by them.
14
+ include InstanceMethods
15
+ include TemporaryToken
22
16
 
23
- # This runs the options block set in the initializer on the model class.
24
- ::Sorcery::Controller::Config.user_config.tap{|blk| blk.call(@sorcery_config) if blk}
17
+ include_required_submodules!
25
18
 
26
- init_mongoid_support! if defined?(Mongoid) and self.ancestors.include?(Mongoid::Document)
27
- init_mongo_mapper_support! if defined?(MongoMapper) and self.ancestors.include?(MongoMapper::Document)
19
+ # This runs the options block set in the initializer on the model class.
20
+ ::Sorcery::Controller::Config.user_config.tap{|blk| blk.call(@sorcery_config) if blk}
28
21
 
29
- init_orm_hooks!
22
+ init_mongoid_support! if defined?(Mongoid) and self.ancestors.include?(Mongoid::Document)
23
+ init_mongo_mapper_support! if defined?(MongoMapper) and self.ancestors.include?(MongoMapper::Document)
24
+ init_datamapper_support! if defined?(DataMapper) and self.ancestors.include?(DataMapper::Resource)
30
25
 
31
- @sorcery_config.after_config << :add_config_inheritance if @sorcery_config.subclasses_inherit_config
32
- @sorcery_config.after_config.each { |c| send(c) }
33
- end
26
+ init_orm_hooks!
34
27
 
35
- protected
36
-
37
- # includes required submodules into the model class,
38
- # which usually is called User.
39
- def include_required_submodules!
40
- self.class_eval do
41
- @sorcery_config.submodules = ::Sorcery::Controller::Config.submodules
42
- @sorcery_config.submodules.each do |mod|
43
- begin
44
- include Submodules.const_get(mod.to_s.split("_").map {|p| p.capitalize}.join(""))
45
- rescue NameError
46
- # don't stop on a missing submodule. Needed because some submodules are only defined
47
- # in the controller side.
48
- end
49
- end
50
- end
51
- end
28
+ @sorcery_config.after_config << :add_config_inheritance if @sorcery_config.subclasses_inherit_config
29
+ @sorcery_config.after_config.each { |c| send(c) }
30
+ end
52
31
 
53
- # defines mongoid fields on the model class,
54
- # using 1.8.x hash syntax to perserve compatibility.
55
- def init_mongoid_support!
56
- self.class_eval do
57
- sorcery_config.username_attribute_names.each do |username|
58
- field username, :type => String
59
- end
60
- field sorcery_config.email_attribute_name, :type => String unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
61
- field sorcery_config.crypted_password_attribute_name, :type => String
62
- field sorcery_config.salt_attribute_name, :type => String
63
- end
32
+ protected
33
+
34
+ # includes required submodules into the model class,
35
+ # which usually is called User.
36
+ def include_required_submodules!
37
+ self.class_eval do
38
+ @sorcery_config.submodules = ::Sorcery::Controller::Config.submodules
39
+ @sorcery_config.submodules.each do |mod|
40
+ begin
41
+ include Submodules.const_get(mod.to_s.split('_').map {|p| p.capitalize}.join)
42
+ rescue NameError
43
+ # don't stop on a missing submodule. Needed because some submodules are only defined
44
+ # in the controller side.
64
45
  end
46
+ end
47
+ end
48
+ end
65
49
 
66
- # defines mongo_mapper fields on the model class,
67
- def init_mongo_mapper_support!
68
- self.class_eval do
69
- sorcery_config.username_attribute_names.each do |username|
70
- key username, String
71
- end
72
- key sorcery_config.email_attribute_name, String unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
73
- key sorcery_config.crypted_password_attribute_name, String
74
- key sorcery_config.salt_attribute_name, String
75
- end
76
- end
50
+ # defines mongoid fields on the model class,
51
+ # using 1.8.x hash syntax to perserve compatibility.
52
+ def init_mongoid_support!
53
+ self.class_eval do
54
+ sorcery_config.username_attribute_names.each do |username|
55
+ field username, :type => String
56
+ end
57
+ field sorcery_config.email_attribute_name, :type => String unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
58
+ field sorcery_config.crypted_password_attribute_name, :type => String
59
+ field sorcery_config.salt_attribute_name, :type => String
60
+ end
61
+ end
77
62
 
78
- # add virtual password accessor and ORM callbacks.
79
- def init_orm_hooks!
80
- self.class_eval do
81
- attr_accessor @sorcery_config.password_attribute_name
82
- #attr_protected @sorcery_config.crypted_password_attribute_name, @sorcery_config.salt_attribute_name
83
- before_save :encrypt_password, :if => Proc.new { |record|
84
- record.send(sorcery_config.password_attribute_name).present?
85
- }
86
- after_save :clear_virtual_password, :if => Proc.new { |record|
87
- record.send(sorcery_config.password_attribute_name).present?
88
- }
89
- end
63
+ # defines mongo_mapper fields on the model class,
64
+ def init_mongo_mapper_support!
65
+ self.class_eval do
66
+ sorcery_config.username_attribute_names.each do |username|
67
+ key username, String
68
+ end
69
+ key sorcery_config.email_attribute_name, String unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
70
+ key sorcery_config.crypted_password_attribute_name, String
71
+ key sorcery_config.salt_attribute_name, String
72
+ end
73
+ end
74
+
75
+ # defines datamapper fields on the model class
76
+ def init_datamapper_support!
77
+ self.class_eval do
78
+ sorcery_config.username_attribute_names.each do |username|
79
+ property username, String, :length => 255
80
+ end
81
+ unless sorcery_config.username_attribute_names.include?(sorcery_config.email_attribute_name)
82
+ property sorcery_config.email_attribute_name, String, :length => 255
83
+ end
84
+ property sorcery_config.crypted_password_attribute_name, String, :length => 255
85
+ property sorcery_config.salt_attribute_name, String, :length => 255
86
+ end
87
+ end
88
+
89
+ # add virtual password accessor and ORM callbacks.
90
+ def init_orm_hooks!
91
+ if defined?(DataMapper) and self.ancestors.include?(DataMapper::Resource)
92
+ init_datamapper_hooks!
93
+ return
94
+ end
95
+ self.class_eval do
96
+ attr_accessor @sorcery_config.password_attribute_name
97
+ #attr_protected @sorcery_config.crypted_password_attribute_name, @sorcery_config.salt_attribute_name
98
+ before_save :encrypt_password, :if => Proc.new { |record|
99
+ record.send(sorcery_config.password_attribute_name).present?
100
+ }
101
+ after_save :clear_virtual_password, :if => Proc.new { |record|
102
+ record.send(sorcery_config.password_attribute_name).present?
103
+ }
104
+ end
105
+ end
106
+
107
+ def init_datamapper_hooks!
108
+ self.class_eval do
109
+ attr_accessor @sorcery_config.password_attribute_name
110
+ before :valid? do
111
+ if self.send(sorcery_config.password_attribute_name).present?
112
+ encrypt_password
113
+ end
114
+ end
115
+ after :save do
116
+ if self.send(sorcery_config.password_attribute_name).present?
117
+ clear_virtual_password
90
118
  end
91
119
  end
92
120
  end
@@ -183,6 +211,10 @@ module Sorcery
183
211
  def clear_virtual_password
184
212
  config = sorcery_config
185
213
  self.send(:"#{config.password_attribute_name}=", nil)
214
+
215
+ if respond_to?(:"#{config.password_attribute_name}_confirmation=")
216
+ self.send(:"#{config.password_attribute_name}_confirmation=", nil)
217
+ end
186
218
  end
187
219
 
188
220
  # calls the requested email method on the configured mailer
@@ -19,12 +19,17 @@ module Sorcery
19
19
  def update_single_attribute(name, value)
20
20
  update_many_attributes(name => value)
21
21
  end
22
+
23
+ def sorcery_save(options = {})
24
+ mthd = options.delete(:raise_on_failure) ? :save! : :save
25
+ self.send(mthd, options)
26
+ end
22
27
  end
23
28
 
24
29
  module ClassMethods
25
30
  def column_name(attribute)
26
31
  return "LOWER(#{attribute})" if (@sorcery_config.downcase_username_before_authenticating)
27
- return "#{attribute}"
32
+ attribute.to_s
28
33
  end
29
34
 
30
35
  def find_by_credentials(credentials)
@@ -37,7 +42,7 @@ module Sorcery
37
42
  end
38
43
 
39
44
  def get_current_users
40
- config = sorcery_config
45
+ config = @sorcery_config
41
46
  where("#{config.last_activity_at_attribute_name} IS NOT NULL") \
42
47
  .where("#{config.last_logout_at_attribute_name} IS NULL OR #{config.last_activity_at_attribute_name} > #{config.last_logout_at_attribute_name}") \
43
48
  .where("#{config.last_activity_at_attribute_name} > ? ", config.activity_timeout.seconds.ago.utc.to_s(:db))
@@ -0,0 +1,123 @@
1
+ module Sorcery
2
+ module Model
3
+ module Adapters
4
+ module DataMapper
5
+ def self.included(klass)
6
+ klass.extend ClassMethods
7
+ klass.send(:include, InstanceMethods)
8
+ end
9
+
10
+ module InstanceMethods
11
+ def increment(attr)
12
+ self[attr] ||= 0
13
+ self[attr] += 1
14
+ self
15
+ end
16
+
17
+ def update_many_attributes(attrs)
18
+ attrs.each do |name, value|
19
+ value = value.utc if value.is_a?(ActiveSupport::TimeWithZone)
20
+ self.send(:"#{name}=", value)
21
+ end
22
+ self.class.get(self.id).update(attrs)
23
+ end
24
+
25
+ def update_single_attribute(name, value)
26
+ update_many_attributes(name => value)
27
+ end
28
+
29
+ def sorcery_save(options = {})
30
+ if options.key?(:validate) && ! options[:validate]
31
+ save!
32
+ else
33
+ save
34
+ end
35
+ end
36
+ end
37
+
38
+ module ClassMethods
39
+ def find(id)
40
+ get(id)
41
+ end
42
+
43
+ def delete_all
44
+ destroy
45
+ end
46
+
47
+ # NOTE
48
+ # DM Adapter dependent
49
+ # DM creates MySQL tables case insensitive by default
50
+ # http://datamapper.lighthouseapp.com/projects/20609-datamapper/tickets/1105
51
+ def find_by_credentials(credentials)
52
+ credential = credentials[0].dup
53
+ credential.downcase! if @sorcery_config.downcase_username_before_authenticating
54
+ @sorcery_config.username_attribute_names.each do |name|
55
+ @user = first(name => credential)
56
+ break if @user
57
+ end
58
+ !!@user ? get(@user.id) : nil
59
+ end
60
+
61
+ def find_by_provider_and_uid(provider, uid)
62
+ @user_klass ||= ::Sorcery::Controller::Config.user_class.to_s.constantize
63
+ user = first(@user_klass.sorcery_config.provider_attribute_name => provider, @user_klass.sorcery_config.provider_uid_attribute_name => uid)
64
+ !!user ? get(user.id) : nil
65
+ end
66
+
67
+ def find_by_id(id)
68
+ find(id)
69
+ rescue ::DataMapper::ObjectNotFoundError
70
+ nil
71
+ end
72
+
73
+ def find_by_activation_token(token)
74
+ user = first(sorcery_config.activation_token_attribute_name => token)
75
+ !!user ? get(user.id) : nil
76
+ end
77
+
78
+ def find_by_remember_me_token(token)
79
+ user = first(sorcery_config.remember_me_token_attribute_name => token)
80
+ !!user ? get(user.id) : nil
81
+ end
82
+
83
+ def find_by_username(username)
84
+ user = nil
85
+ sorcery_config.username_attribute_names.each do |name|
86
+ user = first(name => username)
87
+ break if user
88
+ end
89
+ !!user ? get(user.id) : nil
90
+ end
91
+
92
+ def transaction(&blk)
93
+ tap(&blk)
94
+ end
95
+
96
+ def find_by_sorcery_token(token_attr_name, token)
97
+ user = first(token_attr_name => token)
98
+ !!user ? get(user.id) : nil
99
+ end
100
+
101
+ def find_by_email(email)
102
+ user = first(sorcery_config.email_attribute_name => email)
103
+ !!user ? get(user.id) : nil
104
+ end
105
+
106
+ # NOTE
107
+ # DM Adapter dependent
108
+ def get_current_users
109
+ unless self.repository.adapter.is_a?(::DataMapper::Adapters::MysqlAdapter)
110
+ raise 'Unsupported DataMapper Adapter'
111
+ end
112
+ config = sorcery_config
113
+ ret = all(config.last_logout_at_attribute_name => nil) |
114
+ all(config.last_activity_at_attribute_name.gt => config.last_logout_at_attribute_name)
115
+ ret = ret.all(config.last_activity_at_attribute_name.not => nil)
116
+ ret = ret.all(config.last_activity_at_attribute_name.gt => config.activity_timeout.seconds.ago.utc)
117
+ ret
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -5,15 +5,19 @@ module Sorcery
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- include Sorcery::Model
8
+ extend Sorcery::Model
9
9
  end
10
10
 
11
11
  def increment(attr)
12
12
  self.class.increment(id, attr => 1)
13
13
  end
14
14
 
15
- def save!(options = {})
16
- save(options)
15
+ def sorcery_save(options = {})
16
+ if options.delete(:raise_on_failure) && options[:validate] != false
17
+ save! options
18
+ else
19
+ save options
20
+ end
17
21
  end
18
22
 
19
23
  def update_many_attributes(attrs)
@@ -22,7 +26,7 @@ module Sorcery
22
26
 
23
27
  module ClassMethods
24
28
  def credential_regex(credential)
25
- return { :$regex => /^#{credential}$/i } if (@sorcery_config.downcase_username_before_authenticating)
29
+ return { :$regex => /^#{Regexp.escape(credential)}$/i } if (@sorcery_config.downcase_username_before_authenticating)
26
30
  return credential
27
31
  end
28
32
 
@@ -2,11 +2,6 @@ module Sorcery
2
2
  module Model
3
3
  module Adapters
4
4
  module Mongoid
5
- def self.included(klass)
6
- klass.extend ClassMethods
7
- klass.send(:include, InstanceMethods)
8
- end
9
-
10
5
  module InstanceMethods
11
6
  def increment(attr)
12
7
  self.inc(attr, 1)
@@ -23,11 +18,16 @@ module Sorcery
23
18
  def update_single_attribute(name, value)
24
19
  update_many_attributes(name => value)
25
20
  end
21
+
22
+ def sorcery_save(options = {})
23
+ mthd = options.delete(:raise_on_failure) ? :save! : :save
24
+ self.send(mthd, options)
25
+ end
26
26
  end
27
27
 
28
28
  module ClassMethods
29
29
  def credential_regex(credential)
30
- return { :$regex => /^#{credential}$/i } if (@sorcery_config.downcase_username_before_authenticating)
30
+ return { :$regex => /^#{Regexp.escape(credential)}$/i } if (@sorcery_config.downcase_username_before_authenticating)
31
31
  credential
32
32
  end
33
33
 
@@ -29,6 +29,13 @@ module Sorcery
29
29
  end
30
30
 
31
31
  base.sorcery_config.after_config << :define_activity_logging_mongoid_fields if defined?(Mongoid) and base.ancestors.include?(Mongoid::Document)
32
+ if defined?(DataMapper) and base.ancestors.include?(DataMapper::Resource)
33
+ # NOTE raise exception if data-store is not supported
34
+ unless base.repository.adapter.is_a?(DataMapper::Adapters::MysqlAdapter)
35
+ raise 'Unsupported DataMapper Adapter'
36
+ end
37
+ base.sorcery_config.after_config << :define_activity_logging_datamapper_fields
38
+ end
32
39
  end
33
40
 
34
41
  module ClassMethods
@@ -46,6 +53,23 @@ module Sorcery
46
53
  field sorcery_config.last_activity_at_attribute_name, :type => Time
47
54
  field sorcery_config.last_login_from_ip_address_name, :type => String
48
55
  end
56
+
57
+ def define_activity_logging_datamapper_fields
58
+ property sorcery_config.last_login_at_attribute_name, Time
59
+ property sorcery_config.last_logout_at_attribute_name, Time
60
+ property sorcery_config.last_activity_at_attribute_name, Time
61
+ property sorcery_config.last_login_from_ip_address_name, String
62
+ # Workaround local timezone retrieval problem NOTE dm-core issue #193
63
+ [sorcery_config.last_login_at_attribute_name,
64
+ sorcery_config.last_logout_at_attribute_name,
65
+ sorcery_config.last_activity_at_attribute_name].each do |sym|
66
+ alias_method "orig_#{sym}", sym
67
+ define_method(sym) do
68
+ t = send("orig_#{sym}")
69
+ t && Time.new(t.year, t.month, t.day, t.hour, t.min, t.sec, 0)
70
+ end
71
+ end
72
+ end
49
73
  end
50
74
  end
51
75
  end