anideo-authlogic-connect 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.markdown +234 -0
  3. data/Rakefile +85 -0
  4. data/init.rb +1 -0
  5. data/lib/authlogic-connect.rb +39 -0
  6. data/lib/authlogic_connect/access_token.rb +61 -0
  7. data/lib/authlogic_connect/authlogic_connect.rb +46 -0
  8. data/lib/authlogic_connect/callback_filter.rb +19 -0
  9. data/lib/authlogic_connect/common.rb +10 -0
  10. data/lib/authlogic_connect/common/session.rb +30 -0
  11. data/lib/authlogic_connect/common/state.rb +45 -0
  12. data/lib/authlogic_connect/common/user.rb +77 -0
  13. data/lib/authlogic_connect/common/variables.rb +124 -0
  14. data/lib/authlogic_connect/engine.rb +14 -0
  15. data/lib/authlogic_connect/ext.rb +56 -0
  16. data/lib/authlogic_connect/oauth.rb +14 -0
  17. data/lib/authlogic_connect/oauth/helper.rb +20 -0
  18. data/lib/authlogic_connect/oauth/process.rb +75 -0
  19. data/lib/authlogic_connect/oauth/session.rb +62 -0
  20. data/lib/authlogic_connect/oauth/state.rb +60 -0
  21. data/lib/authlogic_connect/oauth/tokens/aol_token.rb +2 -0
  22. data/lib/authlogic_connect/oauth/tokens/facebook_token.rb +11 -0
  23. data/lib/authlogic_connect/oauth/tokens/foursquare_token.rb +15 -0
  24. data/lib/authlogic_connect/oauth/tokens/get_satisfaction_token.rb +9 -0
  25. data/lib/authlogic_connect/oauth/tokens/github_token.rb +14 -0
  26. data/lib/authlogic_connect/oauth/tokens/google_token.rb +41 -0
  27. data/lib/authlogic_connect/oauth/tokens/linked_in_token.rb +19 -0
  28. data/lib/authlogic_connect/oauth/tokens/meetup_token.rb +12 -0
  29. data/lib/authlogic_connect/oauth/tokens/myspace_token.rb +26 -0
  30. data/lib/authlogic_connect/oauth/tokens/netflix_token.rb +10 -0
  31. data/lib/authlogic_connect/oauth/tokens/oauth_token.rb +164 -0
  32. data/lib/authlogic_connect/oauth/tokens/ohloh_token.rb +9 -0
  33. data/lib/authlogic_connect/oauth/tokens/opensocial_token.rb +0 -0
  34. data/lib/authlogic_connect/oauth/tokens/twitter_token.rb +8 -0
  35. data/lib/authlogic_connect/oauth/tokens/vimeo_token.rb +18 -0
  36. data/lib/authlogic_connect/oauth/tokens/yahoo_token.rb +19 -0
  37. data/lib/authlogic_connect/oauth/user.rb +64 -0
  38. data/lib/authlogic_connect/oauth/variables.rb +64 -0
  39. data/lib/authlogic_connect/openid.rb +11 -0
  40. data/lib/authlogic_connect/openid/process.rb +74 -0
  41. data/lib/authlogic_connect/openid/session.rb +56 -0
  42. data/lib/authlogic_connect/openid/state.rb +48 -0
  43. data/lib/authlogic_connect/openid/tokens/aol_token.rb +0 -0
  44. data/lib/authlogic_connect/openid/tokens/blogger_token.rb +0 -0
  45. data/lib/authlogic_connect/openid/tokens/flickr_token.rb +0 -0
  46. data/lib/authlogic_connect/openid/tokens/my_openid_token.rb +3 -0
  47. data/lib/authlogic_connect/openid/tokens/openid_token.rb +9 -0
  48. data/lib/authlogic_connect/openid/user.rb +38 -0
  49. data/lib/authlogic_connect/openid/variables.rb +19 -0
  50. data/lib/authlogic_connect/rack_state.rb +19 -0
  51. data/lib/open_id_authentication.rb +127 -0
  52. data/rails/init.rb +19 -0
  53. data/test/controllers/test_users_controller.rb +21 -0
  54. data/test/libs/database.rb +47 -0
  55. data/test/libs/user.rb +7 -0
  56. data/test/libs/user_session.rb +2 -0
  57. data/test/test_helper.rb +178 -0
  58. data/test/test_oauth.rb +178 -0
  59. data/test/test_openid.rb +71 -0
  60. data/test/test_user.rb +85 -0
  61. metadata +232 -0
@@ -0,0 +1,19 @@
1
+ class AuthlogicConnect::CallbackFilter
2
+ def initialize(app)
3
+ @app = app
4
+ end
5
+
6
+ # this intercepts how the browser interprets the url.
7
+ # so we override it and say,
8
+ # "if we've stored a variable in the session called :auth_callback_method,
9
+ # then convert that into a POST call so we re-call the original method"
10
+ def call(env)
11
+ if env["rack.session"].nil?
12
+ raise "Make sure you are setting the session in Rack too! Place this in config/application.rb"
13
+ end
14
+ unless env["rack.session"][:auth_callback_method].blank?
15
+ env["REQUEST_METHOD"] = env["rack.session"].delete(:auth_callback_method).to_s.upcase
16
+ end
17
+ @app.call(env)
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module AuthlogicConnect::Common
2
+ end
3
+
4
+ require File.dirname(__FILE__) + "/common/state"
5
+ require File.dirname(__FILE__) + "/common/variables"
6
+ require File.dirname(__FILE__) + "/common/user"
7
+ require File.dirname(__FILE__) + "/common/session"
8
+
9
+ ActiveRecord::Base.send(:include, AuthlogicConnect::Common::User)
10
+ Authlogic::Session::Base.send(:include, AuthlogicConnect::Common::Session)
@@ -0,0 +1,30 @@
1
+ module AuthlogicConnect::Common
2
+ module Session
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ include Variables
7
+ include InstanceMethods
8
+ end
9
+ end
10
+
11
+ module InstanceMethods
12
+
13
+ # core save method coordinating how to save the session.
14
+ # want to destroy the block if we redirect to a remote service, that's it.
15
+ # otherwise the block contains the render methods we wan to use
16
+ def save(&block)
17
+ self.errors.clear
18
+ # log_state
19
+ authenticate_via_protocol(block_given?) do |redirecting|
20
+ block = nil if redirecting
21
+ result = super(&block)
22
+ cleanup_authentication_session unless block.nil?
23
+ result
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ # This class holds query/state variables common to oauth and openid
2
+ module AuthlogicConnect::Common::State
3
+
4
+ def auth_controller?
5
+ !auth_controller.blank?
6
+ end
7
+
8
+ def auth_params?
9
+ auth_controller? && !auth_params.blank?
10
+ end
11
+
12
+ def auth_session?
13
+ !auth_session.blank?
14
+ end
15
+
16
+ def is_auth_session?
17
+ self.is_a?(Authlogic::Session::Base)
18
+ end
19
+
20
+ def start_authentication?
21
+ start_oauth? || start_openid?
22
+ end
23
+
24
+ def validate_password_with_oauth?
25
+ !using_openid? && super
26
+ end
27
+
28
+ def validate_password_with_openid?
29
+ !using_oauth? && super
30
+ end
31
+
32
+ # because user and session are so closely tied together, I am still
33
+ # uncertain as to how they are saved. So this makes sure if we are
34
+ # logging in, it must be saving the session, otherwise the user.
35
+ def correct_request_class?
36
+ return false unless auth_params?
37
+
38
+ if is_auth_session?
39
+ auth_type.to_s == "session"
40
+ else
41
+ auth_type.to_s == "user"
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,77 @@
1
+ # This class is the main api for the user.
2
+ # It is also required to properly sequence the save methods
3
+ # for the different authentication types (oauth and openid)
4
+ module AuthlogicConnect::Common::User
5
+
6
+ def self.included(base)
7
+ base.class_eval do
8
+ add_acts_as_authentic_module(InstanceMethods, :append)
9
+ add_acts_as_authentic_module(AuthlogicConnect::Common::Variables, :prepend)
10
+ end
11
+ end
12
+
13
+ module InstanceMethods
14
+
15
+ def self.included(base)
16
+ base.class_eval do
17
+ has_many :access_tokens, :class_name => "AccessToken", :dependent => :destroy
18
+ belongs_to :active_token, :class_name => "AccessToken", :dependent => :destroy
19
+ accepts_nested_attributes_for :access_tokens, :active_token
20
+ end
21
+ end
22
+
23
+ def authenticated_with
24
+ @authenticated_with ||= self.access_tokens.collect{|t| t.service_name.to_s}
25
+ end
26
+
27
+ def authenticated_with?(service)
28
+ self.access_tokens.detect{|t| t.service_name.to_s == service.to_s}
29
+ end
30
+
31
+ def update_attributes(attributes, &block)
32
+ self.attributes = attributes
33
+ save(:validate => true, &block)
34
+ end
35
+
36
+ def has_token?(service_name)
37
+ !get_token(service_name).nil?
38
+ end
39
+
40
+ def get_token(service_name)
41
+ self.access_tokens.detect {|i| i.service_name.to_s == service_name.to_s}
42
+ end
43
+
44
+ # core save method coordinating how to save the user.
45
+ # we dont' want to ru validations based on the
46
+ # authentication mission we are trying to accomplish.
47
+ # instead, we just return save as false.
48
+ # the next time around, when we recieve the callback,
49
+ # we will run the validations.
50
+ # when you call 'current_user_session' in ApplicationController,
51
+ # it leads to calling 'save' on this User object via "session.record.save",
52
+ # from the 'persisting?' method. So we don't want any of this to occur
53
+ # when that save is called, and the only way to check currently is
54
+ # to check if there is a block_given?
55
+ def save(options = {}, &block)
56
+ self.errors.clear
57
+ # log_state
58
+ options = {} if options == false
59
+ options[:validate] = true unless options.has_key?(:validate)
60
+ save_options = ActiveRecord::VERSION::MAJOR < 3 ? options[:validate] : options
61
+
62
+ # kill the block if we're starting authentication
63
+ authenticate_via_protocol(block_given?, options) do |start_authentication|
64
+ block = nil if start_authentication # redirecting
65
+ # forces you to validate, only if a block is given
66
+ result = super(save_options) # validate!
67
+ unless block.nil?
68
+ cleanup_authentication_session(options)
69
+ yield(result)
70
+ end
71
+ result
72
+ end
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,124 @@
1
+ module AuthlogicConnect::Common::Variables
2
+ include AuthlogicConnect::Common::State
3
+
4
+ attr_reader :processing_authentication
5
+
6
+ def auth_class
7
+ is_auth_session? ? self.class : session_class
8
+ end
9
+
10
+ def auth_controller
11
+ is_auth_session? ? controller : session_class.controller
12
+ end
13
+
14
+ def auth_params
15
+ return nil unless auth_controller?
16
+ auth_controller.params.symbolize_keys!
17
+ auth_controller.params.keys.each do |key|
18
+ auth_controller.params[key.to_s] = auth_controller.params.delete(key) if key.to_s =~ /^OpenID/
19
+ end
20
+ auth_controller.params
21
+ end
22
+
23
+ def auth_session
24
+ return nil unless auth_controller?
25
+ auth_controller.session.symbolize_keys!
26
+ auth_controller.session.keys.each do |key|
27
+ auth_controller.session[key.to_s] = auth_controller.session.delete(key) if key.to_s =~ /^OpenID/
28
+ end
29
+ auth_controller.session
30
+ end
31
+
32
+ def auth_callback_url(options = {})
33
+ auth_controller.url_for({:controller => auth_controller.controller_name, :action => auth_controller.action_name}.merge(options))
34
+ end
35
+
36
+ # if we've said it's a "user" (registration), or a "session" (login)
37
+ def auth_type
38
+ from_session_or_params(:authentication_type)
39
+ end
40
+
41
+ # auth_params and auth_session attributes are all String!
42
+ def from_session_or_params(attribute)
43
+ return nil unless auth_controller?
44
+ key = attribute.is_a?(Symbol) ? attribute : attribute.to_sym
45
+ result = auth_params[key] if (auth_params && auth_params[key])
46
+ result = auth_session[key] if (result.nil? || result.blank?)
47
+ result
48
+ end
49
+
50
+ def add_session_key(key, value)
51
+
52
+ end
53
+
54
+ def remove_session_key(key)
55
+ keys = key.is_a?(Symbol) ? [key, key.to_s] : [key, key.to_sym]
56
+ keys.each {|k| auth_session.delete(k)}
57
+ end
58
+
59
+ # wraps the call to "save" (in yield).
60
+ # reason being, we need to somehow not allow oauth/openid validations to run
61
+ # when we don't have a block. We can't know that using class methods, so we create
62
+ # this property "processing_authentication", which is used in the validation method.
63
+ # it's value is set to "block_given", which is the value of block_given?
64
+ def authenticate_via_protocol(block_given = false, options = {}, &block)
65
+ @processing_authentication = auth_controller? && block_given
66
+ saved = yield start_authentication?
67
+ @processing_authentication = false
68
+ saved
69
+ end
70
+
71
+ # returns boolean
72
+ def authentication_protocol(with, phase)
73
+ returning(send("#{phase.to_s}_#{with.to_s}?")) do |ready|
74
+ send("#{phase.to_s}_#{with.to_s}") if ready
75
+ end if send("using_#{with.to_s}?")
76
+ end
77
+
78
+ # it only reaches this point once it has returned, or you
79
+ # have manually skipped the redirect and save was called directly.
80
+ def cleanup_authentication_session(options = {}, &block)
81
+ unless (options.has_key?(:keep_session) && options[:keep_session])
82
+ %w(oauth openid).each do |type|
83
+ send("cleanup_#{type.to_s}_session")
84
+ end
85
+ end
86
+ end
87
+
88
+ def log(*methods)
89
+ methods.each do |method|
90
+ puts "#{method.to_s}: #{send(method).inspect}"
91
+ end
92
+ end
93
+
94
+ def log_state
95
+ log(:correct_request_class?)
96
+ log(:using_oauth?, :start_oauth?, :complete_oauth?)
97
+ log(:oauth_request?, :oauth_response?, :stored_oauth_token_and_secret?)
98
+ log(:using_openid?, :start_openid?, :complete_openid?, :openid_request?, :openid_response?)
99
+ log(:authenticating_with_openid?)
100
+ log(:stored_oauth_token_and_secret)
101
+ end
102
+
103
+ # because we may need to store 6+ session variables, all with pretty lengthy names,
104
+ # might as well just tinify them.
105
+ # just an idea
106
+ def optimized_session_key(key)
107
+ @optimized_session_keys ||= {
108
+ :auth_request_class => :authcl,
109
+ :authentication_method => :authme,
110
+ :authentication_type => :authty,
111
+ :oauth_provider => :authpr,
112
+ :auth_callback_method => :authcb,
113
+ :oauth_request_token => :authtk,
114
+ :oauth_request_token_secret => :authsc,
115
+ :auth_attributes => :authat
116
+ }
117
+ @optimized_session_keys[key]
118
+ end
119
+
120
+ def auto_register?
121
+ true
122
+ end
123
+
124
+ end
@@ -0,0 +1,14 @@
1
+ module AuthlogicConnect
2
+ class Engine < Rails::Engine
3
+
4
+ initializer "authlogic_connect.authentication_hook" do |app|
5
+ app.middleware.use AuthlogicConnect::CallbackFilter
6
+ app.middleware.use OpenIdAuthentication
7
+ end
8
+
9
+ initializer "authlogic_connect.finalize", :after => "authlogic_connect.authentication_hook" do |app|
10
+ OpenID::Util.logger = Rails.logger
11
+ ActionController::Base.send :include, OpenIdAuthentication
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ # these are extensions I've found useful for this project
2
+ class String
3
+ # normalizes an OpenID according to http://openid.net/specs/openid-authentication-2_0.html#normalization
4
+ def normalize_identifier
5
+ # clean up whitespace
6
+ identifier = self.dup.strip
7
+
8
+ # if an XRI has a prefix, strip it.
9
+ identifier.gsub!(/xri:\/\//i, '')
10
+
11
+ # dodge XRIs -- TODO: validate, don't just skip.
12
+ unless ['=', '@', '+', '$', '!', '('].include?(identifier.at(0))
13
+ # does it begin with http? if not, add it.
14
+ identifier = "http://#{identifier}" unless identifier =~ /^http/i
15
+
16
+ # strip any fragments
17
+ identifier.gsub!(/\#(.*)$/, '')
18
+
19
+ begin
20
+ uri = URI.parse(identifier)
21
+ uri.scheme = uri.scheme.downcase # URI should do this
22
+ identifier = uri.normalize.to_s
23
+ rescue URI::InvalidURIError
24
+ raise InvalidOpenId.new("#{identifier} is not an OpenID identifier")
25
+ end
26
+ end
27
+
28
+ return identifier
29
+ end
30
+ end
31
+
32
+ class Hash
33
+ def recursively_symbolize_keys!
34
+ self.symbolize_keys!
35
+ self.values.each do |v|
36
+ if v.is_a? Hash
37
+ v.recursively_symbolize_keys!
38
+ elsif v.is_a? Array
39
+ v.recursively_symbolize_keys!
40
+ end
41
+ end
42
+ self
43
+ end
44
+ end
45
+
46
+ class Array
47
+ def recursively_symbolize_keys!
48
+ self.each do |item|
49
+ if item.is_a? Hash
50
+ item.recursively_symbolize_keys!
51
+ elsif item.is_a? Array
52
+ item.recursively_symbolize_keys!
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,14 @@
1
+ module AuthlogicConnect::Oauth
2
+ end
3
+
4
+ require File.dirname(__FILE__) + "/oauth/state"
5
+ require File.dirname(__FILE__) + "/oauth/variables"
6
+ require File.dirname(__FILE__) + "/oauth/process"
7
+ require File.dirname(__FILE__) + "/oauth/user"
8
+ require File.dirname(__FILE__) + "/oauth/session"
9
+ require File.dirname(__FILE__) + "/oauth/helper"
10
+
11
+ ActiveRecord::Base.send(:include, AuthlogicConnect::Oauth::User)
12
+ Authlogic::Session::Base.send(:include, AuthlogicConnect::Oauth::Session)
13
+ ActionController::Base.helper AuthlogicConnect::Oauth::Helper
14
+ ActionView::Helpers::FormBuilder.send(:include, AuthlogicConnect::Oauth::FormHelper)
@@ -0,0 +1,20 @@
1
+ module AuthlogicConnect::Oauth::Helper
2
+
3
+ # options include "name"
4
+ def oauth_register_hidden_input
5
+ oauth_input(:type => "user")
6
+ end
7
+
8
+ def oauth_login_hidden_input
9
+ oauth_input(:type => "session")
10
+ end
11
+
12
+ def oauth_input(options = {})
13
+ tag(:input, {:type => "hidden", :name => "authentication_type", :value => options[:type]})
14
+ end
15
+
16
+ end
17
+
18
+ module AuthlogicConnect::Oauth::FormHelper
19
+
20
+ end
@@ -0,0 +1,75 @@
1
+ module AuthlogicConnect::Oauth::Process
2
+
3
+ include AuthlogicConnect::Oauth::Variables
4
+
5
+ # Step 2: after save is called, it runs this method for validation
6
+ def validate_by_oauth
7
+ if processing_authentication
8
+ authentication_protocol(:oauth, :start) || authentication_protocol(:oauth, :complete)
9
+ end
10
+ end
11
+
12
+ # Step 3: if new_oauth_request?, redirect to oauth provider
13
+ def start_oauth
14
+ save_oauth_session
15
+ authorize_url = token_class.authorize_url(auth_callback_url) do |request_token|
16
+ save_auth_session_token(request_token) # only for oauth version 1
17
+ end
18
+ auth_controller.redirect_to authorize_url
19
+ end
20
+
21
+ # Step 4: on callback, run this method
22
+ def complete_oauth
23
+ # implemented in User and Session Oauth modules
24
+ unless new_oauth_request? # shouldn't be validating if it's redirecting...
25
+ restore_attributes
26
+ complete_oauth_transaction
27
+ return true
28
+ end
29
+ return false
30
+ end
31
+
32
+ # Step 3a: save our passed-parameters into the session,
33
+ # so we can retrieve them after the redirect calls back
34
+ def save_oauth_session
35
+ # Store the class which is redirecting, so we can ensure other classes
36
+ # don't get confused and attempt to use the response
37
+ auth_session[:auth_request_class] = self.class.name
38
+
39
+ auth_session[:authentication_type] = auth_params[:authentication_type]
40
+ auth_session[:oauth_provider] = auth_params[:oauth_provider]
41
+ auth_session[:auth_method] = "oauth"
42
+
43
+ # Tell our rack callback filter what method the current request is using
44
+ auth_session[:auth_callback_method] = auth_controller.request.method
45
+ end
46
+
47
+ # Step 3b (if version 1.0 of oauth)
48
+ def save_auth_session_token(request)
49
+ # store token and secret
50
+ auth_session[:oauth_request_token] = request.token
51
+ auth_session[:oauth_request_token_secret] = request.secret
52
+ end
53
+
54
+ def restore_attributes
55
+ end
56
+
57
+ # Step last, after the response
58
+ # having lots of trouble testing logging and out multiple times,
59
+ # so there needs to be a solid way to know when a user has messed up loggin in.
60
+ def cleanup_oauth_session
61
+ [:auth_request_class,
62
+ :authentication_type,
63
+ :auth_method,
64
+ :auth_attributes,
65
+ :oauth_provider,
66
+ :auth_callback_method,
67
+ :oauth_request_token,
68
+ :oauth_request_token_secret,
69
+ :_key,
70
+ :_token,
71
+ :_secret,
72
+ ].each {|key| remove_session_key(key)}
73
+ end
74
+
75
+ end