sorcery 0.1.4 → 0.3.1
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 -2
- data/Gemfile.lock +16 -13
- data/README.rdoc +149 -78
- data/Rakefile +5 -0
- data/VERSION +1 -1
- data/lib/generators/sorcery_migration/sorcery_migration_generator.rb +24 -0
- data/lib/generators/sorcery_migration/templates/activity_logging.rb +17 -0
- data/lib/generators/sorcery_migration/templates/brute_force_protection.rb +11 -0
- data/lib/generators/sorcery_migration/templates/core.rb +16 -0
- data/lib/generators/sorcery_migration/templates/external.rb +14 -0
- data/lib/generators/sorcery_migration/templates/remember_me.rb +15 -0
- data/lib/generators/sorcery_migration/templates/reset_password.rb +13 -0
- data/lib/generators/sorcery_migration/templates/user_activation.rb +17 -0
- data/lib/sorcery/controller/adapters/sinatra.rb +97 -0
- data/lib/sorcery/controller/submodules/activity_logging.rb +20 -7
- data/lib/sorcery/controller/submodules/brute_force_protection.rb +9 -2
- data/lib/sorcery/controller/submodules/email.rb +44 -0
- data/lib/sorcery/controller/submodules/external/protocols/oauth1.rb +35 -0
- data/lib/sorcery/controller/submodules/external/protocols/oauth2.rb +26 -0
- data/lib/sorcery/controller/submodules/external/providers/facebook.rb +82 -0
- data/lib/sorcery/controller/submodules/external/providers/twitter.rb +86 -0
- data/lib/sorcery/controller/submodules/external.rb +83 -0
- data/lib/sorcery/controller/submodules/http_basic_auth.rb +18 -9
- 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 +6 -1
- data/lib/sorcery/controller.rb +30 -16
- data/lib/sorcery/engine.rb +9 -2
- data/lib/sorcery/model/submodules/activity_logging.rb +13 -8
- data/lib/sorcery/model/submodules/brute_force_protection.rb +11 -8
- data/lib/sorcery/model/submodules/external.rb +53 -0
- data/lib/sorcery/model/submodules/remember_me.rb +5 -3
- data/lib/sorcery/model/submodules/reset_password.rb +16 -13
- data/lib/sorcery/model/submodules/user_activation.rb +38 -19
- data/lib/sorcery/model/temporary_token.rb +22 -0
- data/lib/sorcery/model.rb +17 -7
- data/lib/sorcery/sinatra.rb +14 -0
- data/lib/sorcery/test_helpers/rails.rb +57 -0
- data/lib/sorcery/test_helpers/sinatra.rb +131 -0
- data/lib/sorcery/test_helpers.rb +40 -0
- data/lib/sorcery.rb +22 -0
- data/sorcery.gemspec +146 -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} +4 -4
- data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +23 -3
- data/spec/rails3/app_root/app/controllers/application_controller.rb +42 -1
- 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 -10
- data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
- data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
- data/spec/rails3/app_root/db/migrate/external/20101224223628_create_authentications.rb +14 -0
- data/spec/rails3/{controller_activity_logging_spec.rb → app_root/spec/controller_activity_logging_spec.rb} +13 -13
- data/spec/rails3/{controller_brute_force_protection_spec.rb → app_root/spec/controller_brute_force_protection_spec.rb} +16 -6
- data/spec/rails3/{controller_http_basic_auth_spec.rb → app_root/spec/controller_http_basic_auth_spec.rb} +3 -3
- data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +119 -0
- data/spec/rails3/app_root/spec/controller_oauth_spec.rb +122 -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} +6 -6
- data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +20 -13
- data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
- data/spec/rails3/app_root/spec/spec_helper.rb +62 -0
- data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +60 -20
- data/spec/rails3/{user_activity_logging_spec.rb → app_root/spec/user_activity_logging_spec.rb} +4 -4
- data/spec/rails3/{user_brute_force_protection_spec.rb → app_root/spec/user_brute_force_protection_spec.rb} +7 -7
- 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/{user_reset_password_spec.rb → app_root/spec/user_reset_password_spec.rb} +21 -41
- data/spec/rails3/app_root/spec/user_spec.rb +317 -0
- data/spec/sinatra/Gemfile +12 -0
- data/spec/sinatra/Gemfile.lock +134 -0
- data/spec/sinatra/Rakefile +10 -0
- data/spec/sinatra/authentication.rb +3 -0
- data/spec/sinatra/db/migrate/activation/20101224223622_add_activation_to_users.rb +17 -0
- data/spec/sinatra/db/migrate/activity_logging/20101224223624_add_activity_logging_to_users.rb +17 -0
- data/spec/sinatra/db/migrate/brute_force_protection/20101224223626_add_brute_force_protection_to_users.rb +11 -0
- data/spec/sinatra/db/migrate/core/20101224223620_create_users.rb +16 -0
- data/spec/sinatra/db/migrate/external/20101224223628_create_authentications.rb +14 -0
- data/spec/sinatra/db/migrate/remember_me/20101224223623_add_remember_me_token_to_users.rb +15 -0
- data/spec/sinatra/db/migrate/reset_password/20101224223622_add_reset_password_to_users.rb +13 -0
- data/spec/sinatra/filters.rb +21 -0
- data/spec/sinatra/myapp.rb +133 -0
- data/spec/sinatra/sorcery_mailer.rb +25 -0
- data/spec/sinatra/spec/controller_activity_logging_spec.rb +85 -0
- data/spec/sinatra/spec/controller_brute_force_protection_spec.rb +69 -0
- data/spec/sinatra/spec/controller_http_basic_auth_spec.rb +53 -0
- data/spec/sinatra/spec/controller_oauth2_spec.rb +119 -0
- data/spec/sinatra/spec/controller_oauth_spec.rb +121 -0
- data/spec/sinatra/spec/controller_remember_me_spec.rb +64 -0
- data/spec/sinatra/spec/controller_session_timeout_spec.rb +52 -0
- data/spec/sinatra/spec/controller_spec.rb +120 -0
- data/spec/sinatra/spec/spec.opts +4 -0
- data/spec/sinatra/spec/spec_helper.rb +44 -0
- data/spec/sinatra/spec/user_activation_spec.rb +188 -0
- data/spec/sinatra/spec/user_activity_logging_spec.rb +36 -0
- data/spec/sinatra/spec/user_brute_force_protection_spec.rb +76 -0
- data/spec/sinatra/spec/user_oauth_spec.rb +39 -0
- data/spec/sinatra/spec/user_remember_me_spec.rb +66 -0
- data/spec/sinatra/spec/user_reset_password_spec.rb +178 -0
- data/spec/{rails3 → sinatra/spec}/user_spec.rb +68 -38
- data/spec/sinatra/user.rb +6 -0
- data/spec/sinatra/views/test_login.erb +4 -0
- data/spec/untitled folder +18 -0
- metadata +203 -58
- 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/spec_helper.rb +0 -135
- /data/spec/rails3/{Rakefile → app_root/Rakefile} +0 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Controller
|
|
3
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 controller part of the submodule, which adds hooks to register user events,
|
|
9
|
+
# and methods to collect active users data for use in the app.
|
|
10
|
+
# see Socery::Model::Submodules::ActivityLogging for configuration options.
|
|
4
11
|
module ActivityLogging
|
|
5
12
|
def self.included(base)
|
|
6
13
|
base.send(:include, InstanceMethods)
|
|
@@ -10,33 +17,39 @@ module Sorcery
|
|
|
10
17
|
end
|
|
11
18
|
|
|
12
19
|
module InstanceMethods
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
# Returns an array of the active users.
|
|
21
|
+
def current_users
|
|
22
|
+
Config.user_class.current_users
|
|
15
23
|
# A possible patch here:
|
|
16
|
-
# we'll add the
|
|
24
|
+
# we'll add the current_user to the users list if he's not in it (can happen when he was inactive for more than activity timeout):
|
|
17
25
|
#
|
|
18
|
-
# users.unshift!(
|
|
26
|
+
# users.unshift!(current_user) if logged_in? && users.find {|u| u.id == current_user.id}.nil?
|
|
19
27
|
#
|
|
20
28
|
# disadvantages: can hurt performance.
|
|
21
29
|
end
|
|
22
30
|
|
|
23
31
|
protected
|
|
24
32
|
|
|
33
|
+
# registers last login time on every login.
|
|
34
|
+
# This runs as a hook just after a successful login.
|
|
25
35
|
def register_login_time_to_db(user, credentials)
|
|
26
36
|
user.send(:"#{user.sorcery_config.last_login_at_attribute_name}=", Time.now.utc.to_s(:db))
|
|
27
37
|
user.save!(:validate => false)
|
|
28
38
|
end
|
|
29
39
|
|
|
40
|
+
# registers last logout time on every logout.
|
|
41
|
+
# This runs as a hook just before a logout.
|
|
30
42
|
def register_logout_time_to_db(user)
|
|
31
43
|
user.send(:"#{user.sorcery_config.last_logout_at_attribute_name}=", Time.now.utc.to_s(:db))
|
|
32
44
|
user.save!(:validate => false)
|
|
33
45
|
end
|
|
34
46
|
|
|
35
|
-
#
|
|
47
|
+
# Updates last activity time on every request.
|
|
48
|
+
# The only exception is logout - we do not update activity on logout
|
|
36
49
|
def register_last_activity_time_to_db
|
|
37
50
|
return if !logged_in?
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
current_user.send(:"#{current_user.sorcery_config.last_activity_at_attribute_name}=", Time.now.utc.to_s(:db))
|
|
52
|
+
current_user.save!(:validate => false)
|
|
40
53
|
end
|
|
41
54
|
end
|
|
42
55
|
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Controller
|
|
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 controller part of the submodule which takes care of updating the failed logins and resetting them.
|
|
6
|
+
# See Sorcery::Model::Submodules::BruteForceProtection for configuration options.
|
|
4
7
|
module BruteForceProtection
|
|
5
8
|
def self.included(base)
|
|
6
9
|
base.send(:include, InstanceMethods)
|
|
@@ -13,13 +16,17 @@ module Sorcery
|
|
|
13
16
|
|
|
14
17
|
protected
|
|
15
18
|
|
|
19
|
+
# Increments the failed logins counter on every failed login.
|
|
20
|
+
# Runs as a hook after a failed login.
|
|
16
21
|
def update_failed_logins_count!(credentials)
|
|
17
|
-
user =
|
|
22
|
+
user = Config.user_class.where("#{Config.user_class.sorcery_config.username_attribute_name} = ?", credentials[0]).first
|
|
18
23
|
user.register_failed_login! if user
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# Resets the failed logins counter.
|
|
27
|
+
# Runs as a hook after a successful login.
|
|
21
28
|
def reset_failed_logins_count!(user, credentials)
|
|
22
|
-
user.update_attributes!(
|
|
29
|
+
user.update_attributes!(Config.user_class.sorcery_config.failed_logins_count_attribute_name => 0)
|
|
23
30
|
end
|
|
24
31
|
end
|
|
25
32
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# module Sorcery
|
|
2
|
+
# module Controller
|
|
3
|
+
# module Submodules
|
|
4
|
+
# # This module allows you to authenticate to the site using a mail server.
|
|
5
|
+
# # Let's say your organization is called blup, and everyone has someklutz@blup.com,
|
|
6
|
+
# # Instead of registering a new username and password for an internal web application,
|
|
7
|
+
# # why not simply reuse your email credentials?
|
|
8
|
+
# # Of course this means that if the mail server is down, nobody can authenticate to your app.
|
|
9
|
+
# # I might add a fallback for that in the future.
|
|
10
|
+
# module Email
|
|
11
|
+
# def self.included(base)
|
|
12
|
+
# base.send(:include, InstanceMethods)
|
|
13
|
+
# Config.module_eval do
|
|
14
|
+
# class << self
|
|
15
|
+
# attr_reader :email_protocols # email protocols like pop3, IMAP etc.
|
|
16
|
+
#
|
|
17
|
+
# def merge_mail_defaults!
|
|
18
|
+
# @defaults.merge!(:@email_protocols => [])
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# def email_protocols=(protocols)
|
|
22
|
+
# protocols.each do |protocol|
|
|
23
|
+
# include Protocols.const_get(protocol.to_s.split("_").map {|p| p.capitalize}.join(""))
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
# merge_mail_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 login_at(protocol)
|
|
37
|
+
#
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# end
|
|
41
|
+
# end
|
|
42
|
+
# end
|
|
43
|
+
# end
|
|
44
|
+
# end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
require 'oauth'
|
|
2
|
+
module Sorcery
|
|
3
|
+
module Controller
|
|
4
|
+
module Submodules
|
|
5
|
+
module External
|
|
6
|
+
module Protocols
|
|
7
|
+
module Oauth1
|
|
8
|
+
def oauth_version
|
|
9
|
+
"1.0"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def get_request_token(token=nil,secret=nil)
|
|
13
|
+
return ::OAuth::RequestToken.new(get_consumer,token,secret) if token && secret
|
|
14
|
+
get_consumer.get_request_token(:oauth_callback => @callback_url)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def authorize_url(args)
|
|
18
|
+
get_request_token(args[:request_token],args[:request_token_secret]).authorize_url(:oauth_callback => @callback_url)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get_access_token(args)
|
|
22
|
+
get_request_token(args[:request_token],args[:request_token_secret]).get_access_token(:oauth_verifier => args[:oauth_verifier])
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
protected
|
|
26
|
+
|
|
27
|
+
def get_consumer
|
|
28
|
+
::OAuth::Consumer.new(@key, @secret, :site => @site)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'oauth2'
|
|
2
|
+
module Sorcery
|
|
3
|
+
module Controller
|
|
4
|
+
module Submodules
|
|
5
|
+
module External
|
|
6
|
+
module Protocols
|
|
7
|
+
module Oauth2
|
|
8
|
+
def oauth_version
|
|
9
|
+
"2.0"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def authorize_url(*args)
|
|
13
|
+
client = ::OAuth2::Client.new(@key, @secret, :site => @site)
|
|
14
|
+
client.web_server.authorize_url(:redirect_uri => @callback_url, :scope => @scope)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def get_access_token(args)
|
|
18
|
+
client = ::OAuth2::Client.new(@key, @secret, :site => @site)
|
|
19
|
+
client.web_server.get_access_token(args[:code], :redirect_uri => @callback_url)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Controller
|
|
3
|
+
module Submodules
|
|
4
|
+
module External
|
|
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 Protocols::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
|
|
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']
|
|
53
|
+
user_hash
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def has_callback?
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# calculates and returns the url to which the user should be redirected,
|
|
61
|
+
# to get authenticated at the external provider's site.
|
|
62
|
+
def login_url(params,session)
|
|
63
|
+
self.authorize_url
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# tries to login the user from access token
|
|
67
|
+
def process_callback(params,session)
|
|
68
|
+
args = {}
|
|
69
|
+
args.merge!({:code => params[:code]}) if params[:code]
|
|
70
|
+
@access_token = self.get_access_token(args)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
init
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Controller
|
|
3
|
+
module Submodules
|
|
4
|
+
module External
|
|
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
|
|
19
|
+
# def twitter(&blk) # allows block syntax.
|
|
20
|
+
# yield @twitter
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
def merge_twitter_defaults!
|
|
24
|
+
@defaults.merge!(:@twitter => TwitterClient)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
merge_twitter_defaults!
|
|
28
|
+
update!
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module TwitterClient
|
|
33
|
+
class << self
|
|
34
|
+
attr_accessor :key,
|
|
35
|
+
:secret,
|
|
36
|
+
:callback_url,
|
|
37
|
+
:site,
|
|
38
|
+
:user_info_path,
|
|
39
|
+
:user_info_mapping
|
|
40
|
+
|
|
41
|
+
include Protocols::Oauth1
|
|
42
|
+
|
|
43
|
+
def init
|
|
44
|
+
@site = "https://api.twitter.com"
|
|
45
|
+
@user_info_path = "/1/account/verify_credentials.json"
|
|
46
|
+
@user_info_mapping = {}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def get_user_hash
|
|
50
|
+
user_hash = {}
|
|
51
|
+
response = @access_token.get(@user_info_path)
|
|
52
|
+
user_hash[:user_info] = JSON.parse(response.body)
|
|
53
|
+
user_hash[:uid] = user_hash[:user_info]['id']
|
|
54
|
+
user_hash
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def has_callback?
|
|
58
|
+
true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# calculates and returns the url to which the user should be redirected,
|
|
62
|
+
# to get authenticated at the external provider's site.
|
|
63
|
+
def login_url(params,session)
|
|
64
|
+
req_token = self.get_request_token
|
|
65
|
+
session[:request_token] = req_token.token
|
|
66
|
+
session[:request_token_secret] = req_token.secret
|
|
67
|
+
self.authorize_url({:request_token => req_token.token, :request_token_secret => req_token.secret})
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# tries to login the user from access token
|
|
71
|
+
def process_callback(params,session)
|
|
72
|
+
args = {}
|
|
73
|
+
args.merge!({:oauth_verifier => params[:oauth_verifier], :request_token => session[:request_token], :request_token_secret => session[:request_token_secret]})
|
|
74
|
+
args.merge!({:code => params[:code]}) if params[:code]
|
|
75
|
+
@access_token = self.get_access_token(args)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
end
|
|
79
|
+
init
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module Sorcery
|
|
2
|
+
module Controller
|
|
3
|
+
module Submodules
|
|
4
|
+
# This submodule helps you login users from external auth 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
|
+
module External
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.send(:include, InstanceMethods)
|
|
9
|
+
Config.module_eval do
|
|
10
|
+
class << self
|
|
11
|
+
attr_reader :external_providers # external providers like twitter.
|
|
12
|
+
|
|
13
|
+
def merge_external_defaults!
|
|
14
|
+
@defaults.merge!(:@external_providers => [])
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def external_providers=(providers)
|
|
18
|
+
providers.each do |provider|
|
|
19
|
+
include Providers.const_get(provider.to_s.split("_").map {|p| p.capitalize}.join(""))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
merge_external_defaults!
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module InstanceMethods
|
|
28
|
+
protected
|
|
29
|
+
|
|
30
|
+
# sends user to authenticate at the provider's website.
|
|
31
|
+
# after authentication the user is redirected to the callback defined in the provider config
|
|
32
|
+
def login_at(provider, args = {})
|
|
33
|
+
@provider = Config.send(provider)
|
|
34
|
+
if @provider.has_callback?
|
|
35
|
+
redirect_to @provider.login_url(params,session)
|
|
36
|
+
else
|
|
37
|
+
#@provider.login(args)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# tries to login the user from provider's callback
|
|
42
|
+
def login_from(provider)
|
|
43
|
+
@provider = Config.send(provider)
|
|
44
|
+
@provider.process_callback(params,session)
|
|
45
|
+
@user_hash = @provider.get_user_hash
|
|
46
|
+
if user = Config.user_class.load_from_provider(provider,@user_hash[:uid])
|
|
47
|
+
reset_session
|
|
48
|
+
login_user(user)
|
|
49
|
+
user
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# this method automatically creates a new user from the data in the external user hash.
|
|
54
|
+
# The mappings from user hash fields to user db fields are set at controller config.
|
|
55
|
+
# If the hash field you would like to map is nested, use slashes. For example, Given a hash like:
|
|
56
|
+
#
|
|
57
|
+
# "user" => {"name"=>"moishe"}
|
|
58
|
+
#
|
|
59
|
+
# You will set the mapping:
|
|
60
|
+
#
|
|
61
|
+
# {:username => "user/name"}
|
|
62
|
+
#
|
|
63
|
+
# And this will cause 'moishe' to be set as the value of :username field.
|
|
64
|
+
def create_from(provider)
|
|
65
|
+
provider = provider.to_sym
|
|
66
|
+
@provider = Config.send(provider)
|
|
67
|
+
@user_hash = @provider.get_user_hash
|
|
68
|
+
config = Config.user_class.sorcery_config
|
|
69
|
+
attrs = {}
|
|
70
|
+
@provider.user_info_mapping.each do |k,v|
|
|
71
|
+
(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])
|
|
72
|
+
end
|
|
73
|
+
Config.user_class.transaction do
|
|
74
|
+
@user = Config.user_class.create!(attrs)
|
|
75
|
+
Config.user_class.sorcery_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]})
|
|
76
|
+
end
|
|
77
|
+
@user
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
module Sorcery
|
|
2
2
|
module Controller
|
|
3
3
|
module Submodules
|
|
4
|
+
# This submodule integrates HTTP Basic authentication into sorcery.
|
|
5
|
+
# You are provided with a before filter, require_login_from_http_basic, which requests the browser for authentication.
|
|
6
|
+
# Then the rest of the submodule takes care of logging the user in into the session, so that the next requests will keep him logged in.
|
|
4
7
|
module HttpBasicAuth
|
|
5
8
|
def self.included(base)
|
|
6
9
|
base.send(:include, InstanceMethods)
|
|
@@ -31,25 +34,31 @@ module Sorcery
|
|
|
31
34
|
def require_login_from_http_basic
|
|
32
35
|
(request_http_basic_authentication(realm_name_by_controller) and (session[:http_authentication_used] = true) and return) if (request.authorization.nil? || session[:http_authentication_used].nil?)
|
|
33
36
|
require_login
|
|
37
|
+
session[:http_authentication_used] = nil unless logged_in?
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
# given to main controller module as a login source callback
|
|
37
41
|
def login_from_basic_auth
|
|
38
42
|
authenticate_with_http_basic do |username, password|
|
|
39
|
-
@
|
|
40
|
-
login_user(@
|
|
41
|
-
@
|
|
43
|
+
@current_user = (Config.user_class.authenticate(username, password) if session[:http_authentication_used]) || false
|
|
44
|
+
login_user(@current_user) if @current_user
|
|
45
|
+
@current_user
|
|
42
46
|
end
|
|
43
47
|
end
|
|
44
48
|
|
|
49
|
+
# Sets the realm name by searching the controller name in the hash given at configuration time.
|
|
45
50
|
def realm_name_by_controller
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
if defined?(ActionController::Base)
|
|
52
|
+
current_controller = self.class
|
|
53
|
+
while current_controller != ActionController::Base
|
|
54
|
+
result = Config.controller_to_realm_map[current_controller.controller_name]
|
|
55
|
+
return result if result
|
|
56
|
+
current_controller = self.class.superclass
|
|
57
|
+
end
|
|
58
|
+
nil
|
|
59
|
+
else
|
|
60
|
+
Config.controller_to_realm_map["application"]
|
|
51
61
|
end
|
|
52
|
-
nil
|
|
53
62
|
end
|
|
54
63
|
|
|
55
64
|
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
|
+
def merge_oauth_defaults!
|
|
15
|
+
@defaults.merge!(:@oauth_providers => [])
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def oauth_providers=(providers)
|
|
19
|
+
providers.each do |provider|
|
|
20
|
+
include Providers.const_get(provider.to_s.split("_").map {|p| p.capitalize}.join(""))
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
merge_oauth_defaults!
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
module InstanceMethods
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
# sends user to authenticate at the provider's website.
|
|
32
|
+
# after authentication the user is redirected to the callback defined in the provider config
|
|
33
|
+
def auth_at_provider(provider)
|
|
34
|
+
@provider = Config.send(provider)
|
|
35
|
+
args = {}
|
|
36
|
+
if @provider.respond_to?(:get_request_token)
|
|
37
|
+
req_token = @provider.get_request_token
|
|
38
|
+
session[:request_token] = req_token.token
|
|
39
|
+
session[:request_token_secret] = req_token.secret
|
|
40
|
+
args.merge!({:request_token => req_token.token, :request_token_secret => req_token.secret})
|
|
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], :request_token_secret => session[:request_token_secret]}) 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.user_class.sorcery_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
|