sorcery 0.1.1 → 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.
- data/Gemfile +4 -3
- data/Gemfile.lock +16 -13
- data/LICENSE.txt +1 -1
- data/README.rdoc +77 -63
- data/Rakefile +7 -13
- data/VERSION +1 -1
- data/lib/sorcery/controller/submodules/activity_logging.rb +58 -0
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +15 -69
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +65 -0
- data/lib/sorcery/controller/submodules/oauth/oauth1.rb +25 -0
- data/lib/sorcery/controller/submodules/oauth/oauth2.rb +23 -0
- data/lib/sorcery/controller/submodules/oauth/providers/facebook.rb +64 -0
- data/lib/sorcery/controller/submodules/oauth/providers/twitter.rb +61 -0
- data/lib/sorcery/controller/submodules/oauth.rb +95 -0
- data/lib/sorcery/controller/submodules/remember_me.rb +14 -5
- data/lib/sorcery/controller/submodules/session_timeout.rb +10 -2
- data/lib/sorcery/controller.rb +40 -22
- data/lib/sorcery/crypto_providers/bcrypt.rb +3 -7
- data/lib/sorcery/engine.rb +9 -2
- data/lib/sorcery/model/submodules/activity_logging.rb +40 -0
- data/lib/sorcery/model/submodules/brute_force_protection.rb +79 -0
- data/lib/sorcery/model/submodules/oauth.rb +53 -0
- data/lib/sorcery/model/submodules/remember_me.rb +6 -2
- data/lib/sorcery/model/submodules/reset_password.rb +96 -0
- data/lib/sorcery/model/submodules/user_activation.rb +44 -23
- data/lib/sorcery/model/temporary_token.rb +22 -0
- data/lib/sorcery/model.rb +13 -6
- data/lib/sorcery/test_helpers.rb +84 -0
- data/lib/sorcery.rb +17 -1
- data/sorcery.gemspec +85 -41
- data/spec/Gemfile +3 -2
- data/spec/Gemfile.lock +15 -2
- data/spec/rails3/app_root/.rspec +1 -0
- data/spec/rails3/{Gemfile → app_root/Gemfile} +5 -3
- data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +25 -2
- data/spec/rails3/app_root/app/controllers/application_controller.rb +48 -2
- data/spec/rails3/app_root/app/models/authentication.rb +3 -0
- data/spec/rails3/app_root/app/models/user.rb +4 -1
- data/spec/rails3/app_root/config/application.rb +1 -3
- data/spec/rails3/app_root/config/routes.rb +1 -9
- data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
- data/spec/rails3/app_root/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +17 -0
- data/spec/rails3/app_root/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +11 -0
- data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
- data/spec/rails3/app_root/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
- data/spec/rails3/app_root/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
- data/spec/rails3/app_root/spec/controller_activity_logging_spec.rb +84 -0
- data/spec/rails3/app_root/spec/controller_brute_force_protection_spec.rb +65 -0
- data/spec/rails3/app_root/spec/controller_http_basic_auth_spec.rb +50 -0
- data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +117 -0
- data/spec/rails3/app_root/spec/controller_oauth_spec.rb +117 -0
- data/spec/rails3/{controller_remember_me_spec.rb → app_root/spec/controller_remember_me_spec.rb} +4 -4
- data/spec/rails3/{controller_session_timeout_spec.rb → app_root/spec/controller_session_timeout_spec.rb} +5 -4
- data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +23 -16
- data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
- data/spec/rails3/app_root/spec/spec_helper.rb +61 -0
- data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +62 -22
- data/spec/rails3/app_root/spec/user_activity_logging_spec.rb +36 -0
- data/spec/rails3/app_root/spec/user_brute_force_protection_spec.rb +76 -0
- data/spec/rails3/app_root/spec/user_oauth_spec.rb +39 -0
- data/spec/rails3/{user_remember_me_spec.rb → app_root/spec/user_remember_me_spec.rb} +4 -4
- data/spec/rails3/app_root/spec/user_reset_password_spec.rb +178 -0
- data/spec/rails3/{user_spec.rb → app_root/spec/user_spec.rb} +68 -38
- metadata +145 -61
- data/features/support/env.rb +0 -13
- data/lib/sorcery/model/submodules/password_reset.rb +0 -64
- data/spec/rails3/app_root/db/migrate/password_reset/20101224223622_add_password_reset_to_users.rb +0 -9
- data/spec/rails3/app_root/test/fixtures/users.yml +0 -9
- data/spec/rails3/app_root/test/performance/browsing_test.rb +0 -9
- data/spec/rails3/app_root/test/test_helper.rb +0 -13
- data/spec/rails3/app_root/test/unit/user_test.rb +0 -8
- data/spec/rails3/controller_brute_force_protection_spec.rb +0 -72
- data/spec/rails3/spec_helper.rb +0 -115
- data/spec/rails3/user_password_reset_spec.rb +0 -76
- /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
|
-
|
|
15
|
-
cookies[:remember_me_token] = { :value =>
|
|
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
|
-
|
|
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
|
-
@
|
|
42
|
+
@current_user = user
|
|
34
43
|
else
|
|
35
|
-
@
|
|
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
|
-
#
|
|
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
|
-
@
|
|
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
|
data/lib/sorcery/controller.rb
CHANGED
|
@@ -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
|
|
33
|
+
def require_login
|
|
35
34
|
if !logged_in?
|
|
36
|
-
session[:
|
|
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
|
-
|
|
50
|
+
current_user
|
|
48
51
|
else
|
|
49
|
-
after_failed_login!(
|
|
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
|
-
!!
|
|
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
|
|
68
|
-
@
|
|
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
|
|
72
|
-
|
|
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
|
-
@
|
|
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!(
|
|
98
|
-
Config.after_failed_login.each {|c| self.send(c,
|
|
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
|
-
:
|
|
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
|
-
:@
|
|
153
|
+
:@save_return_to_url => true
|
|
136
154
|
}
|
|
137
155
|
end
|
|
138
156
|
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
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,10 +42,10 @@ 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 ||=
|
|
45
|
+
@cost ||= 1
|
|
50
46
|
end
|
|
51
47
|
attr_writer :cost
|
|
52
|
-
|
|
48
|
+
alias :stretches= :cost=
|
|
53
49
|
|
|
54
50
|
# Creates a BCrypt hash for the password passed.
|
|
55
51
|
def encrypt(*tokens)
|
data/lib/sorcery/engine.rb
CHANGED
|
@@ -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 :
|
|
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
|