sorcery 0.1.4 → 0.2.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.
Files changed (65) hide show
  1. data/Gemfile +4 -2
  2. data/Gemfile.lock +16 -13
  3. data/README.rdoc +30 -27
  4. data/Rakefile +5 -0
  5. data/VERSION +1 -1
  6. data/lib/sorcery/controller/submodules/activity_logging.rb +20 -7
  7. data/lib/sorcery/controller/submodules/brute_force_protection.rb +9 -2
  8. data/lib/sorcery/controller/submodules/http_basic_auth.rb +8 -3
  9. data/lib/sorcery/controller/submodules/oauth/oauth1.rb +26 -0
  10. data/lib/sorcery/controller/submodules/oauth/oauth2.rb +24 -0
  11. data/lib/sorcery/controller/submodules/oauth/providers/facebook.rb +64 -0
  12. data/lib/sorcery/controller/submodules/oauth/providers/twitter.rb +61 -0
  13. data/lib/sorcery/controller/submodules/oauth.rb +92 -0
  14. data/lib/sorcery/controller/submodules/remember_me.rb +14 -5
  15. data/lib/sorcery/controller/submodules/session_timeout.rb +6 -1
  16. data/lib/sorcery/controller.rb +29 -17
  17. data/lib/sorcery/engine.rb +9 -2
  18. data/lib/sorcery/model/submodules/activity_logging.rb +12 -7
  19. data/lib/sorcery/model/submodules/brute_force_protection.rb +11 -4
  20. data/lib/sorcery/model/submodules/oauth.rb +53 -0
  21. data/lib/sorcery/model/submodules/remember_me.rb +5 -3
  22. data/lib/sorcery/model/submodules/reset_password.rb +16 -13
  23. data/lib/sorcery/model/submodules/user_activation.rb +38 -19
  24. data/lib/sorcery/model/temporary_token.rb +22 -0
  25. data/lib/sorcery/model.rb +10 -3
  26. data/lib/sorcery/test_helpers.rb +84 -0
  27. data/lib/sorcery.rb +12 -0
  28. data/sorcery.gemspec +69 -40
  29. data/spec/Gemfile +3 -2
  30. data/spec/Gemfile.lock +15 -2
  31. data/spec/rails3/app_root/.rspec +1 -0
  32. data/spec/rails3/{Gemfile → app_root/Gemfile} +5 -3
  33. data/spec/rails3/{Gemfile.lock → app_root/Gemfile.lock} +25 -2
  34. data/spec/rails3/app_root/app/controllers/application_controller.rb +42 -1
  35. data/spec/rails3/app_root/app/models/authentication.rb +3 -0
  36. data/spec/rails3/app_root/app/models/user.rb +4 -1
  37. data/spec/rails3/app_root/config/application.rb +1 -3
  38. data/spec/rails3/app_root/config/routes.rb +1 -10
  39. data/spec/rails3/app_root/db/migrate/activation/20101224223622_add_activation_to_users.rb +6 -4
  40. data/spec/rails3/app_root/db/migrate/core/20101224223620_create_users.rb +4 -4
  41. data/spec/rails3/app_root/db/migrate/oauth/20101224223628_create_authentications.rb +14 -0
  42. data/spec/rails3/{controller_activity_logging_spec.rb → app_root/spec/controller_activity_logging_spec.rb} +13 -13
  43. data/spec/rails3/{controller_brute_force_protection_spec.rb → app_root/spec/controller_brute_force_protection_spec.rb} +16 -6
  44. data/spec/rails3/{controller_http_basic_auth_spec.rb → app_root/spec/controller_http_basic_auth_spec.rb} +3 -3
  45. data/spec/rails3/app_root/spec/controller_oauth2_spec.rb +117 -0
  46. data/spec/rails3/app_root/spec/controller_oauth_spec.rb +117 -0
  47. data/spec/rails3/{controller_remember_me_spec.rb → app_root/spec/controller_remember_me_spec.rb} +4 -4
  48. data/spec/rails3/{controller_session_timeout_spec.rb → app_root/spec/controller_session_timeout_spec.rb} +4 -4
  49. data/spec/rails3/{controller_spec.rb → app_root/spec/controller_spec.rb} +20 -13
  50. data/spec/rails3/app_root/spec/spec_helper.orig.rb +27 -0
  51. data/spec/rails3/app_root/spec/spec_helper.rb +61 -0
  52. data/spec/rails3/{user_activation_spec.rb → app_root/spec/user_activation_spec.rb} +60 -20
  53. data/spec/rails3/{user_activity_logging_spec.rb → app_root/spec/user_activity_logging_spec.rb} +4 -4
  54. data/spec/rails3/{user_brute_force_protection_spec.rb → app_root/spec/user_brute_force_protection_spec.rb} +7 -7
  55. data/spec/rails3/app_root/spec/user_oauth_spec.rb +39 -0
  56. data/spec/rails3/{user_remember_me_spec.rb → app_root/spec/user_remember_me_spec.rb} +4 -4
  57. data/spec/rails3/{user_reset_password_spec.rb → app_root/spec/user_reset_password_spec.rb} +21 -41
  58. data/spec/rails3/{user_spec.rb → app_root/spec/user_spec.rb} +68 -38
  59. metadata +127 -58
  60. data/spec/rails3/app_root/test/fixtures/users.yml +0 -9
  61. data/spec/rails3/app_root/test/performance/browsing_test.rb +0 -9
  62. data/spec/rails3/app_root/test/test_helper.rb +0 -13
  63. data/spec/rails3/app_root/test/unit/user_test.rb +0 -8
  64. data/spec/rails3/spec_helper.rb +0 -135
  65. /data/spec/rails3/{Rakefile → app_root/Rakefile} +0 -0
data/Gemfile CHANGED
@@ -2,11 +2,13 @@ source "http://rubygems.org"
2
2
  # Add dependencies required to use your gem here.
3
3
  # Example:
4
4
  # gem "activesupport", ">= 2.3.5"
5
-
5
+ gem "rails", ">= 3.0.0"
6
+ gem 'json', ">= 1.5.1"
7
+ gem 'oauth', ">= 0.4.4"
8
+ gem 'oauth2', ">= 0.1.1"
6
9
  # Add dependencies to develop your gem here.
7
10
  # Include everything needed to run rake, tests, features, etc.
8
11
  group :development do
9
- gem "rails", ">= 3.0.0"
10
12
  gem "rspec", "~> 2.3.0"
11
13
  gem 'rspec-rails'
12
14
  gem 'ruby-debug19'
data/Gemfile.lock CHANGED
@@ -28,29 +28,25 @@ GEM
28
28
  activemodel (= 3.0.3)
29
29
  activesupport (= 3.0.3)
30
30
  activesupport (3.0.3)
31
+ addressable (2.2.4)
31
32
  archive-tar-minitar (0.5.2)
32
33
  arel (2.0.6)
33
34
  builder (2.1.2)
34
35
  columnize (0.3.2)
35
- cucumber (0.10.0)
36
- builder (>= 2.1.2)
37
- diff-lcs (~> 1.1.2)
38
- gherkin (~> 2.3.2)
39
- json (~> 1.4.6)
40
- term-ansicolor (~> 1.0.5)
41
36
  diff-lcs (1.1.2)
42
37
  erubis (2.6.6)
43
38
  abstract (>= 1.0.0)
44
- gherkin (2.3.2)
45
- json (~> 1.4.6)
46
- term-ansicolor (~> 1.0.5)
39
+ faraday (0.5.5)
40
+ addressable (~> 2.2.4)
41
+ multipart-post (~> 1.1.0)
42
+ rack (< 2, >= 1.1.0)
47
43
  git (1.2.5)
48
44
  i18n (0.5.0)
49
45
  jeweler (1.5.2)
50
46
  bundler (~> 1.0.0)
51
47
  git (>= 1.2.5)
52
48
  rake
53
- json (1.4.6)
49
+ json (1.5.1)
54
50
  linecache19 (0.5.11)
55
51
  ruby_core_source (>= 0.1.4)
56
52
  mail (2.2.13)
@@ -59,6 +55,12 @@ GEM
59
55
  mime-types (~> 1.16)
60
56
  treetop (~> 1.4.8)
61
57
  mime-types (1.16)
58
+ multi_json (0.0.5)
59
+ multipart-post (1.1.0)
60
+ oauth (0.4.4)
61
+ oauth2 (0.1.1)
62
+ faraday (~> 0.5.0)
63
+ multi_json (~> 0.0.4)
62
64
  polyglot (0.3.1)
63
65
  rack (1.2.1)
64
66
  rack-mount (0.6.13)
@@ -106,7 +108,6 @@ GEM
106
108
  simplecov-html (>= 0.3.7)
107
109
  simplecov-html (0.3.9)
108
110
  sqlite3-ruby (1.3.2)
109
- term-ansicolor (1.0.5)
110
111
  thor (0.14.6)
111
112
  treetop (1.4.9)
112
113
  polyglot (>= 0.3.1)
@@ -118,9 +119,11 @@ PLATFORMS
118
119
 
119
120
  DEPENDENCIES
120
121
  bundler (~> 1.0.0)
121
- cucumber
122
122
  jeweler (~> 1.5.2)
123
- rails (= 3.0.3)
123
+ json (>= 1.5.1)
124
+ oauth (>= 0.4.4)
125
+ oauth2 (>= 0.1.1)
126
+ rails (>= 3.0.0)
124
127
  rspec (~> 2.3.0)
125
128
  rspec-rails
126
129
  ruby-debug19
data/README.rdoc CHANGED
@@ -3,28 +3,47 @@ Magical Authentication for Rails 3.
3
3
 
4
4
  Inspired by restful_authentication, Authlogic and Devise.
5
5
  Crypto code taken almost unchanged from Authlogic.
6
+ OAuth code inspired by OmniAuth.
6
7
 
7
- == Example app using sorcery:
8
+ == Summary
8
9
 
9
- https://github.com/NoamB/sorcery-example-app
10
+ Sorcery aims to make your life easier by giving you an easy API to write your own user authentication flow with.
11
+ It does this with a few goals in mind:
12
+
13
+ * Less is more - less than 20 simple methods to remember for the entire feature-set make the lib easy to 'get'.
14
+ * No built-in or generated code - use the API inside *your own* MVC structures, and don't fight to fix someone else's.
15
+ * Magic yes, Voodoo no - the lib should be easy to hack for most developers.
16
+ * Configuration over Confusion - Simple & short configuration as possible, not drowning in syntactic sugar.
17
+ * Keep MVC cleanly separated - DB is for models, sessions are for controllers. Models stay unaware of sessions.
18
+
19
+ Hopefully, I've achieved this. If not, let me know.
20
+
21
+ == Useful Links:
22
+
23
+ Example app using sorcery: https://github.com/NoamB/sorcery-example-app
24
+
25
+ Documentation: http://rubydoc.info/gems/sorcery/0.2.0/frames
26
+
27
+ Check out the tutorials in the github wiki!
10
28
 
11
29
  == Full Features List by module:
12
30
 
13
31
  Core (see lib/sorcery/model/model.rb and lib/sorcery/controller/controller.rb):
14
- * login/logout, optional redirect on login to where the user tried to reach before, configurable redirect for non-logged-in users.
32
+ * login/logout, optional return user to requested url on login, configurable redirect for non-logged-in users.
15
33
  * password encryption, algorithms: bcrypt(default), md5, sha1, sha256, sha512, aes256, custom(yours!), none. Configurable stretches and salt.
16
34
  * configurable attribute names for username, password and email.
17
35
 
18
36
  User Activation (see lib/sorcery/model/submodules/user_activation.rb):
19
37
  * User activation by email with optional success email.
20
38
  * configurable attribute names.
21
- * configurable mailer.
39
+ * configurable mailer, method name, and attribute name.
40
+ * configurable temporary token expiration.
22
41
  * Optionally prevent non-active users to login.
23
42
 
24
43
  Reset Password (see lib/sorcery/model/submodules/reset_password.rb):
25
44
  * Reset password with email verification.
26
45
  * configurable mailer, method name, and attribute name.
27
- * configurable expiration.
46
+ * configurable temporary token expiration.
28
47
  * configurable time between emails (hammering protection).
29
48
 
30
49
  Remember Me (see lib/sorcery/model/submodules/remember_me.rb):
@@ -49,36 +68,20 @@ Activity Logging (see lib/sorcery/model/submodules/activity_logging.rb):
49
68
  * an easy method of collecting the list of currently logged in users.
50
69
  * configurable timeout by which to decide whether to include a user in the list of logged in users.
51
70
 
52
- Other:
53
- * Modular design, load only the modules you need.
54
- * 100% TDD'd code, 100% test coverage.
71
+ Oauth (see lib/sorcery/controller/submodules/oauth.rb):
72
+ * OAuth1 and OAuth2 support (currently twitter & facebook)
73
+ * configurable db field names and authentications table.
55
74
 
56
75
  == Next Planned Features:
57
76
 
58
- I've got many plans which include:
77
+ I've got many plans which include (by priority):
78
+ * Sinatra support
79
+ * Mongoid support
59
80
  * Configurable Auto login on registration/activation
60
81
  * Other reset password strategies (security questions?)
61
82
  * Other brute force protection strategies (captcha)
62
- * Sinatra support
63
- * Mongoid support
64
- * OAuth1 and OAuth2 support
65
83
  * Have an idea? Let me know, and it might get into the gem!
66
84
 
67
- == Project Goals:
68
-
69
- This gem plugin was started out of a few personal goals which are not related to the problem solved by it at all:
70
- * I wanted to write something 100% TDD from start to finish.
71
- * I wanted to learn how to write an engine for Rails 3.
72
-
73
- In addition to the above goals, when I decided this will be an authentication plugin, and while looking at existing solutions, these goals came up:
74
- * Simple & short configuration as possible, not drowning in syntactic sugar.
75
- * Keep MVC cleanly separated - DB is for models, sessions are for controllers. Models stay unaware of sessions.
76
- * Magic yes, Voodoo no.
77
- * No generated code polluting the application's code.
78
- * No built-in controllers, models, mailers, migrations or templates; Real apps will need all of these custom made.
79
-
80
- Hopefully, I've achieved this. If not, let me know.
81
-
82
85
  == Installation:
83
86
 
84
87
  You can either git clone and then 'rake install' to live on the edge (unstable),
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  # require 'bundler'
2
+ # -- Commented because it's slow
2
3
  # begin
3
4
  # Bundler.setup(:default, :development)
4
5
  # rescue Bundler::BundlerError => e
@@ -6,6 +7,8 @@
6
7
  # $stderr.puts "Run `bundle install` to install missing gems"
7
8
  # exit e.status_code
8
9
  # end
10
+ # --
11
+
9
12
  require 'rake'
10
13
 
11
14
  require 'jeweler'
@@ -23,6 +26,8 @@ Jeweler::Tasks.new do |gem|
23
26
  # gem.add_runtime_dependency 'jabber4r', '> 0.1'
24
27
  # gem.add_development_dependency 'rspec', '> 1.2.3'
25
28
  gem.add_runtime_dependency 'bcrypt-ruby', '~> 2.1.4'
29
+ gem.add_runtime_dependency 'oauth', '>= 0.4.4'
30
+ gem.add_runtime_dependency 'oauth2', '>= 0.1.1'
26
31
  end
27
32
  Jeweler::RubygemsDotOrgTasks.new
28
33
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.4
1
+ 0.2.1
@@ -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
- def logged_in_users
14
- Config.user_class.logged_in_users
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 logged_in_user to the users list if he's not in it (can happen when he was inactive for more than activity timeout):
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!(logged_in_user) if logged_in? && users.find {|u| u.id == logged_in_user.id}.nil?
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
- # we do not update activity on logout
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
- logged_in_user.send(:"#{logged_in_user.sorcery_config.last_activity_at_attribute_name}=", Time.now.utc.to_s(:db))
39
- logged_in_user.save!(:validate => false)
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 = User.where("#{User.sorcery_config.username_attribute_name} = ?", credentials[0]).first
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!(User.sorcery_config.failed_logins_count_attribute_name => 0)
29
+ user.update_attributes!(Config.user_class.sorcery_config.failed_logins_count_attribute_name => 0)
23
30
  end
24
31
  end
25
32
  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,17 +34,19 @@ 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
- @logged_in_user = (Config.user_class.authenticate(username, password) if session[:http_authentication_used]) || false
40
- login_user(@logged_in_user) if @logged_in_user
41
- @logged_in_user
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
51
  current_controller = self.class
47
52
  while current_controller != ActionController::Base
@@ -0,0 +1,26 @@
1
+ require 'oauth'
2
+ module Sorcery
3
+ module Controller
4
+ module Submodules
5
+ module Oauth
6
+ module Oauth1
7
+ def oauth_version
8
+ "1.0"
9
+ end
10
+
11
+ def get_request_token
12
+ ::OAuth::Consumer.new(@key, @secret, :site => @site).get_request_token(:oauth_callback => @callback_url)
13
+ end
14
+
15
+ def authorize_url(args)
16
+ args[:request_token].authorize_url(:oauth_callback => @callback_url)
17
+ end
18
+
19
+ def get_access_token(args)
20
+ args[:request_token].get_access_token(:oauth_verifier => args[:oauth_verifier])
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ require 'oauth2'
2
+ module Sorcery
3
+ module Controller
4
+ module Submodules
5
+ module Oauth
6
+ module Oauth2
7
+ def oauth_version
8
+ "2.0"
9
+ end
10
+
11
+ def authorize_url(args)
12
+ client = ::OAuth2::Client.new(@key, @secret, :site => @site)
13
+ client.web_server.authorize_url(:redirect_uri => @callback_url, :scope => @scope)
14
+ end
15
+
16
+ def get_access_token(args)
17
+ client = ::OAuth2::Client.new(@key, @secret, :site => @site)
18
+ client.web_server.get_access_token(args[:code], :redirect_uri => @callback_url)
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -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,92 @@
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
+ if @provider.respond_to?(:get_request_token)
36
+ args = {:request_token => @provider.get_request_token}
37
+ session[:request_token] = args[:request_token]
38
+ end
39
+ redirect_to @provider.authorize_url(args)
40
+ end
41
+
42
+ # tries to login the user from access token
43
+ def login_from_access_token(provider)
44
+ @provider = Config.send(provider)
45
+ args = {}
46
+ args.merge!({:oauth_verifier => params[:oauth_verifier], :request_token => session[:request_token]}) if @provider.respond_to?(:get_request_token)
47
+ args.merge!({:code => params[:code]}) if params[:code]
48
+ @access_token = @provider.get_access_token(args)
49
+ @user_hash = @provider.get_user_hash(@access_token)
50
+ if user = Config.user_class.load_from_provider(provider,@user_hash[:uid])
51
+ reset_session
52
+ login_user(user)
53
+ user
54
+ end
55
+ end
56
+
57
+ def get_user_hash(provider)
58
+ @provider = Config.send(provider)
59
+ @provider.get_user_hash(@access_token)
60
+ end
61
+
62
+ # this method automatically creates a new user from the data in the external user hash.
63
+ # The mappings from user hash fields to user db fields are set at controller config.
64
+ # If the hash field you would like to map is nested, use slashes. For example, Given a hash like:
65
+ #
66
+ # "user" => {"name"=>"moishe"}
67
+ #
68
+ # You will set the mapping:
69
+ #
70
+ # {:username => "user/name"}
71
+ #
72
+ # And this will cause 'moishe' to be set as the value of :username field.
73
+ def create_from_provider!(provider)
74
+ provider = provider.to_sym
75
+ @provider = Config.send(provider)
76
+ @user_hash = get_user_hash(provider)
77
+ config = Config.user_class.sorcery_config
78
+ attrs = {}
79
+ @provider.user_info_mapping.each do |k,v|
80
+ (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])
81
+ end
82
+ Config.user_class.transaction do
83
+ @user = Config.user_class.create!(attrs)
84
+ 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]})
85
+ end
86
+ @user
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ 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