sorcery 0.1.0 → 0.2.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 (75) hide show
  1. data/Gemfile +4 -3
  2. data/Gemfile.lock +16 -13
  3. data/LICENSE.txt +1 -1
  4. data/README.rdoc +77 -63
  5. data/Rakefile +7 -12
  6. data/VERSION +1 -1
  7. data/lib/sorcery/controller/submodules/activity_logging.rb +58 -0
  8. data/lib/sorcery/controller/submodules/brute_force_protection.rb +15 -69
  9. data/lib/sorcery/controller/submodules/http_basic_auth.rb +65 -0
  10. data/lib/sorcery/controller/submodules/oauth/oauth1.rb +25 -0
  11. data/lib/sorcery/controller/submodules/oauth/oauth2.rb +23 -0
  12. data/lib/sorcery/controller/submodules/oauth/providers/facebook.rb +64 -0
  13. data/lib/sorcery/controller/submodules/oauth/providers/twitter.rb +61 -0
  14. data/lib/sorcery/controller/submodules/oauth.rb +95 -0
  15. data/lib/sorcery/controller/submodules/remember_me.rb +14 -5
  16. data/lib/sorcery/controller/submodules/session_timeout.rb +10 -2
  17. data/lib/sorcery/controller.rb +40 -22
  18. data/lib/sorcery/crypto_providers/bcrypt.rb +2 -6
  19. data/lib/sorcery/engine.rb +9 -2
  20. data/lib/sorcery/model/submodules/activity_logging.rb +40 -0
  21. data/lib/sorcery/model/submodules/brute_force_protection.rb +79 -0
  22. data/lib/sorcery/model/submodules/oauth.rb +53 -0
  23. data/lib/sorcery/model/submodules/remember_me.rb +6 -2
  24. data/lib/sorcery/model/submodules/reset_password.rb +96 -0
  25. data/lib/sorcery/model/submodules/user_activation.rb +44 -23
  26. data/lib/sorcery/model/temporary_token.rb +22 -0
  27. data/lib/sorcery/model.rb +22 -9
  28. data/lib/sorcery/test_helpers.rb +84 -0
  29. data/lib/sorcery.rb +17 -1
  30. data/sorcery.gemspec +250 -0
  31. data/spec/Gemfile +3 -2
  32. data/spec/Gemfile.lock +15 -2
  33. data/spec/rails3/app_root/.rspec +1 -0
  34. data/spec/rails3/{Gemfile → app_root/Gemfile} +6 -3
  35. data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +27 -2
  36. data/spec/rails3/app_root/app/controllers/application_controller.rb +48 -2
  37. data/spec/rails3/app_root/app/models/authentication.rb +3 -0
  38. data/spec/rails3/app_root/app/models/user.rb +4 -1
  39. data/spec/rails3/app_root/config/application.rb +1 -3
  40. data/spec/rails3/app_root/config/routes.rb +1 -9
  41. data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
  42. data/spec/rails3/app_root/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +17 -0
  43. data/spec/rails3/app_root/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +11 -0
  44. data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
  45. data/spec/rails3/app_root/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
  46. data/spec/rails3/app_root/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
  47. data/spec/rails3/app_root/spec/controller_activity_logging_spec.rb +84 -0
  48. data/spec/rails3/app_root/spec/controller_brute_force_protection_spec.rb +65 -0
  49. data/spec/rails3/app_root/spec/controller_http_basic_auth_spec.rb +50 -0
  50. data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +117 -0
  51. data/spec/rails3/app_root/spec/controller_oauth_spec.rb +117 -0
  52. data/spec/rails3/{controller_remember_me_spec.rb → app_root/spec/controller_remember_me_spec.rb} +4 -4
  53. data/spec/rails3/{controller_session_timeout_spec.rb → app_root/spec/controller_session_timeout_spec.rb} +5 -4
  54. data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +23 -16
  55. data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
  56. data/spec/rails3/app_root/spec/spec_helper.rb +61 -0
  57. data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +62 -22
  58. data/spec/rails3/app_root/spec/user_activity_logging_spec.rb +36 -0
  59. data/spec/rails3/app_root/spec/user_brute_force_protection_spec.rb +76 -0
  60. data/spec/rails3/app_root/spec/user_oauth_spec.rb +39 -0
  61. data/spec/rails3/{user_remember_me_spec.rb → app_root/spec/user_remember_me_spec.rb} +4 -4
  62. data/spec/rails3/app_root/spec/user_reset_password_spec.rb +178 -0
  63. data/spec/rails3/{user_spec.rb → app_root/spec/user_spec.rb} +75 -41
  64. metadata +147 -102
  65. data/features/support/env.rb +0 -13
  66. data/lib/sorcery/model/submodules/password_reset.rb +0 -64
  67. data/spec/rails3/app_root/db/migrate/password_reset/20101224223622_add_password_reset_to_users.rb +0 -9
  68. data/spec/rails3/app_root/test/fixtures/users.yml +0 -9
  69. data/spec/rails3/app_root/test/performance/browsing_test.rb +0 -9
  70. data/spec/rails3/app_root/test/test_helper.rb +0 -13
  71. data/spec/rails3/app_root/test/unit/user_test.rb +0 -8
  72. data/spec/rails3/controller_brute_force_protection_spec.rb +0 -72
  73. data/spec/rails3/spec_helper.rb +0 -115
  74. data/spec/rails3/user_password_reset_spec.rb +0 -76
  75. /data/spec/rails3/{Rakefile → app_root/Rakefile} +0 -0
@@ -0,0 +1,64 @@
1
+ module Sorcery
2
+ module Controller
3
+ module Submodules
4
+ module Oauth
5
+ module Providers
6
+ # This module adds support for OAuth with facebook.com.
7
+ # When included in the 'config.providers' option, it adds a new option, 'config.facebook'.
8
+ # Via this new option you can configure Facebook specific settings like your app's key and secret.
9
+ #
10
+ # config.facebook.key = <key>
11
+ # config.facebook.secret = <secret>
12
+ # ...
13
+ #
14
+ module Facebook
15
+ def self.included(base)
16
+ base.module_eval do
17
+ class << self
18
+ attr_reader :facebook # access to facebook_client.
19
+
20
+ def merge_facebook_defaults!
21
+ @defaults.merge!(:@facebook => FacebookClient)
22
+ end
23
+ end
24
+ merge_facebook_defaults!
25
+ update!
26
+ end
27
+ end
28
+
29
+ module FacebookClient
30
+ class << self
31
+ attr_accessor :key,
32
+ :secret,
33
+ :callback_url,
34
+ :site,
35
+ :user_info_path,
36
+ :scope,
37
+ :user_info_mapping
38
+
39
+ include Oauth2
40
+
41
+ def init
42
+ @site = "https://graph.facebook.com"
43
+ @user_info_path = "/me"
44
+ @scope = "email,offline_access"
45
+ @user_info_mapping = {}
46
+ end
47
+
48
+ def get_user_hash(access_token)
49
+ user_hash = {}
50
+ response = access_token.get(@user_info_path)
51
+ user_hash[:user_info] = JSON.parse(response)
52
+ user_hash[:uid] = user_hash[:user_info]['id'].to_i
53
+ user_hash
54
+ end
55
+ end
56
+ init
57
+ end
58
+
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,61 @@
1
+ module Sorcery
2
+ module Controller
3
+ module Submodules
4
+ module Oauth
5
+ module Providers
6
+ # This module adds support for OAuth with Twitter.com.
7
+ # When included in the 'config.providers' option, it adds a new option, 'config.twitter'.
8
+ # Via this new option you can configure Twitter specific settings like your app's key and secret.
9
+ #
10
+ # config.twitter.key = <key>
11
+ # config.twitter.secret = <secret>
12
+ # ...
13
+ #
14
+ module Twitter
15
+ def self.included(base)
16
+ base.module_eval do
17
+ class << self
18
+ attr_reader :twitter # access to twitter_client.
19
+
20
+ def merge_twitter_defaults!
21
+ @defaults.merge!(:@twitter => TwitterClient)
22
+ end
23
+ end
24
+ merge_twitter_defaults!
25
+ update!
26
+ end
27
+ end
28
+
29
+ module TwitterClient
30
+ class << self
31
+ attr_accessor :key,
32
+ :secret,
33
+ :callback_url,
34
+ :site,
35
+ :user_info_path,
36
+ :user_info_mapping
37
+
38
+ include Oauth1
39
+
40
+ def init
41
+ @site = "https://api.twitter.com"
42
+ @user_info_path = "/1/account/verify_credentials.json"
43
+ @user_info_mapping = {}
44
+ end
45
+
46
+ def get_user_hash(access_token)
47
+ user_hash = {}
48
+ response = access_token.get(@user_info_path)
49
+ user_hash[:user_info] = JSON.parse(response.body)
50
+ user_hash[:uid] = user_hash[:user_info]['id']
51
+ user_hash
52
+ end
53
+ end
54
+ init
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,95 @@
1
+ module Sorcery
2
+ module Controller
3
+ module Submodules
4
+ # This submodule helps you login users from OAuth providers such as Twitter.
5
+ # This is the controller part which handles the http requests and tokens passed between the app and the provider.
6
+ # For more configuration options see Sorcery::Model::Oauth.
7
+ module Oauth
8
+ def self.included(base)
9
+ base.send(:include, InstanceMethods)
10
+ Config.module_eval do
11
+ class << self
12
+ attr_reader :oauth_providers # oauth providers like twitter.
13
+
14
+ attr_accessor :authentications_class
15
+
16
+ def merge_oauth_defaults!
17
+ @defaults.merge!(:@oauth_providers => [],
18
+ :@authentications_class => nil)
19
+ end
20
+
21
+ def oauth_providers=(providers)
22
+ providers.each do |provider|
23
+ include Providers.const_get(provider.to_s.split("_").map {|p| p.capitalize}.join(""))
24
+ end
25
+ end
26
+ end
27
+ merge_oauth_defaults!
28
+ end
29
+ end
30
+
31
+ module InstanceMethods
32
+ protected
33
+
34
+ # sends user to authenticate at the provider's website.
35
+ # after authentication the user is redirected to the callback defined in the provider config
36
+ def auth_at_provider(provider)
37
+ @provider = Config.send(provider)
38
+ if @provider.respond_to?(:get_request_token)
39
+ args = {:request_token => @provider.get_request_token}
40
+ session[:request_token] = args[:request_token]
41
+ end
42
+ redirect_to @provider.authorize_url(args)
43
+ end
44
+
45
+ # tries to login the user from access token
46
+ def login_from_access_token(provider)
47
+ @provider = Config.send(provider)
48
+ args = {}
49
+ args.merge!({:oauth_verifier => params[:oauth_verifier], :request_token => session[:request_token]}) if @provider.respond_to?(:get_request_token)
50
+ args.merge!({:code => params[:code]}) if params[:code]
51
+ @access_token = @provider.get_access_token(args)
52
+ @user_hash = @provider.get_user_hash(@access_token)
53
+ if user = Config.user_class.load_from_provider(provider,@user_hash[:uid])
54
+ reset_session
55
+ login_user(user)
56
+ user
57
+ end
58
+ end
59
+
60
+ def get_user_hash(provider)
61
+ @provider = Config.send(provider)
62
+ @provider.get_user_hash(@access_token)
63
+ end
64
+
65
+ # this method automatically creates a new user from the data in the external user hash.
66
+ # The mappings from user hash fields to user db fields are set at controller config.
67
+ # If the hash field you would like to map is nested, use slashes. For example, Given a hash like:
68
+ #
69
+ # "user" => {"name"=>"moishe"}
70
+ #
71
+ # You will set the mapping:
72
+ #
73
+ # {:username => "user/name"}
74
+ #
75
+ # And this will cause 'moishe' to be set as the value of :username field.
76
+ def create_from_provider!(provider)
77
+ provider = provider.to_sym
78
+ @provider = Config.send(provider)
79
+ @user_hash = get_user_hash(provider)
80
+ config = Config.user_class.sorcery_config
81
+ attrs = {}
82
+ @provider.user_info_mapping.each do |k,v|
83
+ (varr = v.split("/")).size > 1 ? attrs.merge!(k => varr.inject(@user_hash[:user_info]) {|hsh,v| hsh[v] }) : attrs.merge!(k => @user_hash[:user_info][v])
84
+ end
85
+ Config.user_class.transaction do
86
+ @user = Config.user_class.create!(attrs)
87
+ Config.authentications_class.create!({config.authentications_user_id_attribute_name => @user.id, config.provider_attribute_name => provider, config.provider_uid_attribute_name => @user_hash[:uid]})
88
+ end
89
+ @user
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -1,6 +1,9 @@
1
1
  module Sorcery
2
2
  module Controller
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.
6
+ # See Sorcery::Model::Submodules::RememberMe for configuration options.
4
7
  module RememberMe
5
8
  def self.included(base)
6
9
  base.send(:include, InstanceMethods)
@@ -10,29 +13,35 @@ module Sorcery
10
13
  end
11
14
 
12
15
  module InstanceMethods
16
+ # This method sets the cookie and calls the user to save the token and the expiration to db.
13
17
  def remember_me!
14
- logged_in_user.remember_me!
15
- cookies[:remember_me_token] = { :value => logged_in_user.remember_me_token, :expires => logged_in_user.remember_me_token_expires_at }
18
+ current_user.remember_me!
19
+ cookies[:remember_me_token] = { :value => current_user.remember_me_token, :expires => current_user.remember_me_token_expires_at }
16
20
  end
17
21
 
22
+ # Clears the cookie and clears the token from the db.
18
23
  def forget_me!
19
- logged_in_user.forget_me!
24
+ current_user.forget_me!
20
25
  cookies[:remember_me_token] = nil
21
26
  end
22
27
 
23
28
  protected
24
29
 
30
+ # calls remember_me! if a third credential was passed to the login method.
31
+ # Runs as a hook after login.
25
32
  def remember_me_if_asked_to(user, credentials)
26
33
  remember_me! if credentials.size == 3 && credentials[2]
27
34
  end
28
35
 
36
+ # Checks the cookie for a remember me token, tried to find a user with that token and logs the user in if found.
37
+ # Runs as a login source. See 'current_user' method for how it is used.
29
38
  def login_from_cookie
30
39
  user = cookies[:remember_me_token] && Config.user_class.find_by_remember_me_token(cookies[:remember_me_token])
31
40
  if user && user.remember_me_token?
32
41
  cookies[:remember_me_token] = { :value => user.remember_me_token, :expires => user.remember_me_token_expires_at }
33
- @logged_in_user = user
42
+ @current_user = user
34
43
  else
35
- @logged_in_user = false
44
+ @current_user = false
36
45
  end
37
46
  end
38
47
  end
@@ -1,6 +1,8 @@
1
1
  module Sorcery
2
2
  module Controller
3
3
  module Submodules
4
+ # This submodule helps you set a timeout to all user sessions.
5
+ # The timeout can be configured and also you can choose to reset it on every user action.
4
6
  module SessionTimeout
5
7
  def self.included(base)
6
8
  base.send(:include, InstanceMethods)
@@ -21,20 +23,26 @@ module Sorcery
21
23
  end
22
24
 
23
25
  module InstanceMethods
26
+ protected
27
+
28
+ # Registers last login to be used as the timeout starting point.
29
+ # Runs as a hook after a successful login.
24
30
  def register_login_time(user, credentials)
25
31
  session[:login_time] = session[:last_action_time] = Time.now.utc
26
32
  end
27
33
 
28
- # To be used as a before_filter, before authenticate
34
+ # Checks if session timeout was reached and expires the current session if so.
35
+ # To be used as a before_filter, before require_login
29
36
  def validate_session
30
37
  session_to_use = Config.session_timeout_from_last_action ? session[:last_action_time] : session[:login_time]
31
38
  if session_to_use && (Time.now.utc - session_to_use > Config.session_timeout)
32
39
  reset_session
33
- @logged_in_user = false
40
+ @current_user = false
34
41
  else
35
42
  session[:last_action_time] = Time.now.utc
36
43
  end
37
44
  end
45
+
38
46
  end
39
47
  end
40
48
  end
@@ -5,14 +5,13 @@ module Sorcery
5
5
  extend ClassMethods
6
6
  include InstanceMethods
7
7
  Config.submodules.each do |mod|
8
- begin
8
+ begin # FIXME: is this protection needed?
9
9
  include Submodules.const_get(mod.to_s.split("_").map {|p| p.capitalize}.join(""))
10
10
  rescue NameError
11
11
  # don't stop on a missing submodule.
12
12
  end
13
13
  end
14
14
  Config.update!
15
- Config.user_class = User
16
15
  end
17
16
  end
18
17
 
@@ -31,71 +30,88 @@ module Sorcery
31
30
  # To be used as before_filter.
32
31
  # Will trigger auto-login attempts via the call to logged_in?
33
32
  # If all attempts to auto-login fail, the failure callback will be called.
34
- def require_user_login
33
+ def require_login
35
34
  if !logged_in?
36
- session[:user_wanted_url] = request.url if Config.save_user_wanted_url
35
+ session[:return_to_url] = request.url if Config.save_return_to_url
37
36
  self.send(Config.not_authenticated_action)
38
37
  end
39
38
  end
40
39
 
40
+ # Takes credentials and returns a user on successful authentication.
41
+ # Runs hooks after login or failed login.
41
42
  def login(*credentials)
42
43
  user = Config.user_class.authenticate(*credentials)
43
44
  if user
45
+ return_to_url = session[:return_to_url]
44
46
  reset_session # protect from session fixation attacks
47
+ session[:return_to_url] = return_to_url
45
48
  login_user(user)
46
49
  after_login!(user, credentials)
47
- logged_in_user
50
+ current_user
48
51
  else
49
- after_failed_login!(user, credentials)
52
+ after_failed_login!(credentials)
50
53
  nil
51
54
  end
52
55
  end
53
56
 
57
+ # Resets the session and runs hooks before and after.
54
58
  def logout
55
59
  if logged_in?
60
+ before_logout!(current_user)
56
61
  reset_session
57
62
  after_logout!
58
63
  end
59
64
  end
60
65
 
61
66
  def logged_in?
62
- !!logged_in_user
67
+ !!current_user
63
68
  end
64
69
 
65
70
  # attempts to auto-login from the sources defined (session, basic_auth, cookie, etc.)
66
- # returns the logged in user if found, false if not (using old restful-authentication trick).
67
- def logged_in_user
68
- @logged_in_user ||= login_from_session || login_from_other_sources unless @logged_in_user == false # || login_from_basic_auth || )
71
+ # returns the logged in user if found, false if not (using old restful-authentication trick, nil != false).
72
+ def current_user
73
+ @current_user ||= login_from_session || login_from_other_sources unless @current_user == false
69
74
  end
70
75
 
71
- def login_from_other_sources
72
- result = nil
73
- Config.login_sources.find do |source|
74
- result = send(source)
75
- end
76
- result || false
76
+ def return_or_redirect_to(url, flash_hash = {})
77
+ redirect_to(session[:return_to_url] || url, :flash => flash_hash)
77
78
  end
78
79
 
80
+ # The default action for denying non-authenticated users.
81
+ # You can override this method in your controllers.
79
82
  def not_authenticated
80
83
  redirect_to root_path
81
84
  end
82
85
 
83
86
  protected
84
87
 
88
+ # Tries all available sources (methods) until one doesn't return false.
89
+ def login_from_other_sources
90
+ result = nil
91
+ Config.login_sources.find do |source|
92
+ result = send(source)
93
+ end
94
+ result || false
95
+ end
96
+
85
97
  def login_user(user)
86
98
  session[:user_id] = user.id
87
99
  end
88
100
 
89
101
  def login_from_session
90
- @logged_in_user = (Config.user_class.find_by_id(session[:user_id]) if session[:user_id]) || false
102
+ @current_user = (Config.user_class.find_by_id(session[:user_id]) if session[:user_id]) || false
91
103
  end
92
104
 
93
105
  def after_login!(user, credentials)
94
106
  Config.after_login.each {|c| self.send(c, user, credentials)}
95
107
  end
96
108
 
97
- def after_failed_login!(user, credentials)
98
- Config.after_failed_login.each {|c| self.send(c, user, credentials)}
109
+ def after_failed_login!(credentials)
110
+ Config.after_failed_login.each {|c| self.send(c, credentials)}
111
+ end
112
+
113
+ def before_logout!(user)
114
+ Config.before_logout.each {|c| self.send(c, user)}
99
115
  end
100
116
 
101
117
  def after_logout!
@@ -108,16 +124,17 @@ module Sorcery
108
124
  class << self
109
125
  attr_accessor :submodules,
110
126
 
111
- :user_class, # what class to use as the user class.
127
+ :user_class, # what class to use as the user class. Set automatically when you call activate_sorcery! in the User class.
112
128
 
113
129
  :not_authenticated_action, # what controller action to call for non-authenticated users.
114
130
 
115
- :save_user_wanted_url, # when a non logged in user tries to enter a page that requires login, save the URL he wanted to reach,
131
+ :save_return_to_url, # when a non logged in user tries to enter a page that requires login, save the URL he wanted to reach,
116
132
  # and send him there after login.
117
133
 
118
134
  :login_sources,
119
135
  :after_login,
120
136
  :after_failed_login,
137
+ :before_logout,
121
138
  :after_logout,
122
139
  :after_config
123
140
 
@@ -130,9 +147,10 @@ module Sorcery
130
147
  :@login_sources => [],
131
148
  :@after_login => [],
132
149
  :@after_failed_login => [],
150
+ :@before_logout => [],
133
151
  :@after_logout => [],
134
152
  :@after_config => [],
135
- :@save_user_wanted_url => true
153
+ :@save_return_to_url => true
136
154
  }
137
155
  end
138
156
 
@@ -1,8 +1,4 @@
1
- begin
2
- require "bcrypt"
3
- rescue LoadError
4
- "sudo gem install bcrypt-ruby"
5
- end
1
+ require 'bcrypt'
6
2
 
7
3
  module Sorcery
8
4
  module CryptoProviders
@@ -46,7 +42,7 @@ module Sorcery
46
42
  # This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
47
43
  # Set this to whatever you want, play around with it to get that perfect balance between security and performance.
48
44
  def cost
49
- @cost ||= 10
45
+ @cost ||= 1
50
46
  end
51
47
  attr_writer :cost
52
48
  alias :stretches= :cost=
@@ -13,8 +13,15 @@ module Sorcery
13
13
 
14
14
  initializer "extend Controller with sorcery" do |app|
15
15
  ActionController::Base.send(:include, Sorcery::Controller)
16
- ActionController::Base.helper_method :logged_in_user
16
+ ActionController::Base.helper_method :current_user
17
17
  end
18
-
18
+
19
+ initializer "attempt to preload user model" do |app|
20
+ begin
21
+ require Rails.root + "app/models/user.rb"
22
+ rescue LoadError
23
+ end
24
+ end
25
+
19
26
  end
20
27
  end
@@ -0,0 +1,40 @@
1
+ module Sorcery
2
+ module Model
3
+ module Submodules
4
+ # This submodule keeps track of events such as login, logout, and last activity time, per user.
5
+ # It helps in estimating which users are active now in the site.
6
+ # This cannot be determined absolutely because a user might be reading a page without clicking anything for a while.
7
+
8
+ # This is the model part of the submodule, which provides configuration options.
9
+ module ActivityLogging
10
+ def self.included(base)
11
+ base.extend(ClassMethods)
12
+ base.sorcery_config.class_eval do
13
+ attr_accessor :last_login_at_attribute_name, # last login attribute name.
14
+ :last_logout_at_attribute_name, # last logout attribute name.
15
+ :last_activity_at_attribute_name, # last activity attribute name.
16
+ :activity_timeout # how long since last activity is the user defined logged out?
17
+ end
18
+
19
+ base.sorcery_config.instance_eval do
20
+ @defaults.merge!(:@last_login_at_attribute_name => :last_login_at,
21
+ :@last_logout_at_attribute_name => :last_logout_at,
22
+ :@last_activity_at_attribute_name => :last_activity_at,
23
+ :@activity_timeout => 10.minutes)
24
+ reset!
25
+ end
26
+ end
27
+
28
+ module ClassMethods
29
+ # get all users with last_activity within timeout
30
+ def current_users
31
+ config = sorcery_config
32
+ where("#{config.last_activity_at_attribute_name} IS NOT NULL") \
33
+ .where("#{config.last_logout_at_attribute_name} IS NULL OR #{config.last_activity_at_attribute_name} > #{config.last_logout_at_attribute_name}") \
34
+ .where("#{config.last_activity_at_attribute_name} > ? ", config.activity_timeout.seconds.ago)
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,79 @@
1
+ module Sorcery
2
+ module Model
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.
6
+ module BruteForceProtection
7
+ def self.included(base)
8
+ base.sorcery_config.class_eval do
9
+ attr_accessor :failed_logins_count_attribute_name, # failed logins attribute name.
10
+ :lock_expires_at_attribute_name, # this field indicates whether user is banned and when it will be active again.
11
+ :consecutive_login_retries_amount_limit, # how many failed logins allowed.
12
+ :login_lock_time_period # how long the user should be banned. in seconds. 0 for permanent.
13
+ end
14
+
15
+ base.sorcery_config.instance_eval do
16
+ @defaults.merge!(:@failed_logins_count_attribute_name => :failed_logins_count,
17
+ :@lock_expires_at_attribute_name => :lock_expires_at,
18
+ :@consecutive_login_retries_amount_limit => 50,
19
+ :@login_lock_time_period => 3600)
20
+ reset!
21
+ end
22
+
23
+ base.class_eval do
24
+
25
+ end
26
+
27
+ base.sorcery_config.before_authenticate << :prevent_locked_user_login
28
+ base.extend(ClassMethods)
29
+ base.send(:include, InstanceMethods)
30
+ end
31
+
32
+ module ClassMethods
33
+ protected
34
+
35
+ end
36
+
37
+ module InstanceMethods
38
+ # Called by the controller to increment the failed logins counter.
39
+ # Calls 'lock!' if login retries limit was reached.
40
+ def register_failed_login!
41
+ config = sorcery_config
42
+ return if !unlocked?
43
+ self.increment(config.failed_logins_count_attribute_name)
44
+ save!
45
+ self.lock! if self.send(config.failed_logins_count_attribute_name) >= config.consecutive_login_retries_amount_limit
46
+ end
47
+
48
+ protected
49
+
50
+ def lock!
51
+ config = sorcery_config
52
+ self.update_attributes!(config.lock_expires_at_attribute_name => Time.now.utc + config.login_lock_time_period)
53
+ end
54
+
55
+ def unlock!
56
+ config = sorcery_config
57
+ self.update_attributes!(config.lock_expires_at_attribute_name => nil,
58
+ config.failed_logins_count_attribute_name => 0)
59
+ end
60
+
61
+ def unlocked?
62
+ config = sorcery_config
63
+ self.send(config.lock_expires_at_attribute_name).nil?
64
+ end
65
+
66
+ # Prevents a locked user from logging in, and unlocks users that expired their lock time.
67
+ # Runs as a hook before authenticate.
68
+ def prevent_locked_user_login
69
+ config = sorcery_config
70
+ if !self.unlocked? && config.login_lock_time_period != 0
71
+ self.unlock! if self.send(config.lock_expires_at_attribute_name) <= Time.now.utc
72
+ end
73
+ unlocked?
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -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 => Sorcery::Controller::Config.authentications_class,
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