diff --git a/README.markdown b/README.markdown index 0109c4d..ec6d213 100644 --- a/README.markdown +++ b/README.markdown @@ -11,31 +11,48 @@ By using WardenOmniAuth, you can make use of any of the OmniAuth authentication This is also usable in the [Devise](http://github.com/plataformatec/devise) Rails Engine ## Usage (Rack) -
use OmniAuth::Builer do
+```ruby
+use OmniAuth::Builer do
   # setup omniauth
 end
 
+OmniAuth.config.on_failure = Proc.new do |env|
+  OmniAuth::FailureEndpoint.new(env).redirect_to_failure
+end
+
 use Warden::Manager do |config|
-  # setup warden configuration
+  # setup warden configuration, e.g.:
+  config.failure_app = lambda do |env|
+    # This is also the failure app for any OmniAuth failures
+    Rack::Response.new({:errors => env['warden'].errors.full_messages}.to_json, 401).finish
+  end
+e
 end
 
 use WardenOmniAuth do |config|
-  config.redirect\_after\_callback = "/redirect/path" # default "/"
+  config.redirect_after_callback { |env| env['omniauth.origin'] || "/redirect/path" }
+  # or: config.redirect_after_callback = "/redirect/path"
+end
+
+WardenOmniAuth.on_callback do |omni_user, strategy|
+  # return a user object, e.g.:
+  User.authenticate!(strategy, omni_user['uid'])
 end
 
 run MyApp
-
+``` ## Usage (Devise) -
# config/initializer.rb
+```ruby
+# config/initializer.rb
 Devise.setup do |config|
 config.warden do |manager|
-  [:omni\_twitter, :omni\_facebook, :omni\_github].each do |strategy|
-    manager.default\_strategies(:scope => :user).unshift strategy
+  [:omni_twitter, :omni_facebook, :omni_github].each do |strategy|
+    manager.default_strategies(:scope => :user).unshift strategy
   end
 end
-
+``` This will add the stratgeies to the normal devise user login for github, then facebook, then twitter. @@ -47,27 +64,28 @@ By default, it grabs just _user\\info_, _uid_, _credentials_, _provider_ as a ha If you want to customise this you can do: -

-  WardenOmniAuth.on\_callback do |user|
-    # all callbacks will go here by default
+```ruby
+  WardenOmniAuth.on_callback do |user,strategy|
+    # all callbacks will go here by default;
+    # strategy is something like 'twitter', 'facebook', etc
   end
-
+``` Whatever you return from the block is the user that's made available in warden. ## Dealing with each kind of callback -

+```ruby
 use WardenOmniAuth do |config|
-  Warden::Strategies[:omni\_twitter].on_callback do |user|
+  Warden::Strategies[:omni_twitter].on_callback do |user,strategy|
     # do stuff to get a user and return it from the block
   end
 
-  Warden::Strategies[:omni\_facebook].on_callback do |user|
+  Warden::Strategies[:omni_facebook].on_callback do |user,strategy|
     # do stuff to get a user for a facebook user
   end
 end
-
+``` This will use a specific callback to get the user, or fallback if nothing specific has been defined. diff --git a/lib/warden_omniauth.rb b/lib/warden_omniauth.rb index bfe4ecc..a4b4afc 100644 --- a/lib/warden_omniauth.rb +++ b/lib/warden_omniauth.rb @@ -2,7 +2,7 @@ require 'omniauth' class WardenOmniAuth - DEFAULT_CALLBACK = lambda do |user| + DEFAULT_CALLBACK = lambda do |user, strategy| u = {} u[:info] = user['info'] u[:uid] = user['uid'] @@ -30,7 +30,7 @@ def self.on_callback(&blk) # @example # WardenOmniAuth.setup_strategies(:twitter, :facebook) def self.setup_strategies(*names) - names.map do |name| + names.each do |name| full_name = :"omni_#{name}" unless Warden::Strategies[full_name] klass = Class.new(WardenOmniAuth::Strategy) @@ -44,20 +44,15 @@ def self.setup_strategies(*names) # The base omniauth warden strategy. This is inherited for each # omniauth strategy class Strategy < Warden::Strategies::Base - # make a specific callback for this strategy - def self.on_callback(&blk) - @on_callback = blk if blk - @on_callback || WardenOmniAuth.on_callback - end - - # The name of the OmniAuth strategy to map to - def self.omni_name=(name) - @omni_name = name - end - - # The name of the OmniAuth strategy to map to - def self.omni_name - @omni_name + class << self + # The name of the OmniAuth strategy to map to + attr_accessor :omni_name + + # make a specific callback for this strategy + def on_callback(&blk) + @on_callback = blk if blk + @on_callback || WardenOmniAuth.on_callback + end end def authenticate! @@ -66,51 +61,38 @@ def authenticate! # set the user if one exists # otherwise, redirect for authentication - if user = (env['omniauth.auth'] || env['rack.auth'] || request['auth']) # TODO: Fix.. Completely insecure... do not use this will look in params for the auth. Apparently fixed in the new gem - - success! self.class.on_callback[user] + if user = env['omniauth.auth'] + success! self.class.on_callback[user, self.class.omni_name] else path_prefix = OmniAuth::Configuration.instance.path_prefix - redirect! File.join(path_prefix, self.class.omni_name) + # Pass in the request URI as the 'origin' param, so that + # OmniAuth later provides it under env['omniauth.origin'] + redirect! File.join(path_prefix, self.class.omni_name), { 'origin' => env['REQUEST_URI'] } end end end - # Pulled from extlib - # Convert to snake case. - # - # "FooBar".snake_case #=> "foo_bar" - # "HeadlineCNNNews".snake_case #=> "headline_cnn_news" - # "CNN".snake_case #=> "cnn" - # - # @return [String] Receiver converted to snake case. - # - # @api public - def self.snake_case(string) - return string.downcase if string.match(/\A[A-Z]+\z/) - string.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). - gsub(/([a-z])([A-Z])/, '\1_\2'). - downcase - end - def initialize(app) # setup the warden strategies to wrap the omniauth ones names = OmniAuth::Strategies.constants.map do |konstant| - name = WardenOmniAuth.snake_case(konstant.to_s) + # Create a fake instance of the OmniAuth strategy to be able + # to use the #name method instead of guessing based on the + # class name. + s = OmniAuth::Strategies.const_get(konstant).new(nil) + s.name end WardenOmniAuth.setup_strategies(*names) yield self if block_given? @app = app end - # redirect after a callback def redirect_after_callback=(path) - @redirect_after_callback_path = path + @redirect_after_callback = lambda { |env| path } end - - def redirect_after_callback_path - @redirect_after_callback_path ||= "/" + # redirect after a callback + def redirect_after_callback(&block) + @redirect_after_callback = block end def call(env) @@ -132,7 +114,7 @@ def call(env) args << {:scope => scope.to_sym} if scope response = Rack::Response.new if env['warden'].authenticate? *args - response.redirect(redirect_after_callback_path) + response.redirect(@redirect_after_callback.call(env).to_s) response.finish else auth_path = request.path.gsub(/\/callback$/, "") @@ -143,6 +125,10 @@ def call(env) Rack::Response.new("Bad Session", 400).finish end end + elsif request.path =~ /^#{prefix}\/failure$/i + # query params: message, strategy, origin + env['warden'].errors.add(:login, request.params['message']) + throw :warden else @app.call(env) end diff --git a/test/test_helper.rb b/test/test_helper.rb index e639a2c..4479a4b 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -15,14 +15,20 @@ user end - module MyHelpers def app @app || create_app{|e| Rack::Response.new("OK").finish } end def create_app(&blk) - failure = lambda{|e| Rack::Response.new("Can't login", 401).finish } + failure = lambda do |env| + errors = env['warden'].errors.full_messages + if errors.count > 0 + Rack::Response.new("Can't login: #{errors.join(',')}", 401).finish + else + Rack::Response.new("Can't login", 401).finish + end + end builder = Rack::Builder.new do use Warden::Manager do |config| config.failure_app = failure @@ -33,7 +39,8 @@ def create_app(&blk) use WardenOmniAuth do |config| $omni_auth = config - config.redirect_after_callback = "/redirect/path" + $expected_redirect = "/redirect/path" + config.redirect_after_callback = $expected_redirect end run blk end.to_app diff --git a/test/test_warden_omniauth.rb b/test/test_warden_omniauth.rb index f44c200..871fe90 100644 --- a/test/test_warden_omniauth.rb +++ b/test/test_warden_omniauth.rb @@ -9,11 +9,11 @@ include MyHelpers teardown { @_rack_mock_sessions = nil; @_rack_test_sessions = nil } - # shoudl setup all the omni auth strategies + # should setup all the omni auth strategies test do app OmniAuth::Strategies.constants.each do |klass| - name = WardenOmniAuth.snake_case(klass.to_s) + name = OmniAuth::Strategies.const_get(klass).new(nil).name assert { Warden::Strategies[:"omni_#{name}"] != nil } assert { Warden::Strategies[:"omni_#{name}"].superclass == WardenOmniAuth::Strategy } end @@ -43,7 +43,7 @@ assert { response.body.to_s == "/auth/twitter" } end - # the callback url shoudl be intercepted and should raise if it's unknown + # the callback url should be intercepted and should raise if it's unknown test do assert { Warden::Strategies[:omni_does_not_exist].nil? } response = get "/auth/does_not_exist/callback", {}, {'rack.session' => {}} @@ -54,7 +54,7 @@ # the callback url should be intercepted and should redirect back to the strategy if there is no user # in rack['auth'] test do - response = get "/auth/twitter/callback", {}, { 'rack.auth' => nil, 'rack.session' => {}} + response = get "/auth/twitter/callback", {}, { 'omniauth.auth' => nil, 'rack.session' => {}} assert("status should be 302") { response.status == 302 } assert("url should be /auth/twitter") { response.headers['Location'] == '/auth/twitter' } end @@ -68,6 +68,44 @@ assert("status should be 400" ) { response.status == 400 } assert("body should be bad status" ) { response.body.to_s == "Bad Session" } end + + # The failure app should run if OmniAuth indicates a failure + test do + response = get "/auth/failure", {:strategy => 'twitter', :message => 'Things went south!'}, {'rack.session' => {} } + assert("status should be 401") { response.status == 401 } + assert("text should include 'Can't login'") { response.body.include? "Can't login" } + assert("text should include OmniAuth's failure message") { response.body.include? "Things went south!" } + end + end + + context do + teardown { @_rack_mock_sessions = nil; @_rack_test_sessions = nil } + setup do + $captures = [] + @app = create_app do |e| + e['warden'].authenticate + $captures << e['warden'].user + Rack::Response.new("DONE").finish + end + end + + # The callback should also work as a lambda + test do + session = {} + $omni_auth.redirect_after_callback do |env| + assert("passed env should have omniauth.auth key") { env.has_key? 'omniauth.auth' } + "/path/to/#{env['omniauth.auth']['info']}" + end + + response = get("/auth/twitter/callback", {}, {'rack.session' => session, 'omniauth.auth' => {'info' => "alice"}}) + + assert("should be redirected") { response.status == 302 } + assert("should go to the redirect path"){ response.headers['Location'] == "/path/to/alice" } + + response = get("/path/to/alice", {}, {'rack.session' => session }) + assert("should have made it into the app") { $captures.size == 1 } + assert("should have captured the user"){ $captures.first[:info] == 'alice' } + end end context do @@ -83,17 +121,15 @@ # The session scope should store the user test do - session = {} session[WardenOmniAuth::SCOPE_KEY] = "user" - expected_redirect = $omni_auth.redirect_after_callback_path - response = get("/auth/twitter/callback", {}, {'rack.session' => session, 'rack.auth' => {'info' => "fred"}}) + response = get("/auth/twitter/callback", {}, {'rack.session' => session, 'omniauth.auth' => {'info' => "fred"}}) assert("should be redirected") { response.status == 302 } - assert("should go to the redirect path"){ response.headers['Location'] == expected_redirect } + assert("should go to the redirect path"){ response.headers['Location'] == $expected_redirect } - response = get(expected_redirect, {}, {'rack.session' => session }) + response = get($expected_redirect, {}, {'rack.session' => session }) assert("should have made it into the app") { $captures.size == 1 } assert("should have captured the user"){ $captures.first[:info] == 'fred' } end @@ -103,36 +139,35 @@ begin session = {} session[WardenOmniAuth::SCOPE_KEY] = "user" - expected_redirect = $omni_auth.redirect_after_callback_path - Warden::Strategies[:omni_facebook].on_callback do |user| + Warden::Strategies[:omni_facebook].on_callback do |user,strategy| {:facebook => "user"} end - Warden::Strategies[:omni_twitter].on_callback do |user| + Warden::Strategies[:omni_twitter].on_callback do |user,strategy| {:twitter => "user"} end - Warden::Strategies[:omni_google_oauth2].on_callback do |user| + Warden::Strategies[:omni_google_oauth2].on_callback do |user,strategy| {:google_oauth2 => "user"} end - response = get("/auth/facebook/callback", {}, {'rack.session' => session, 'rack.auth' => {'info' => "fred"}}) - response = get expected_redirect, {}, {'rack.session' => session} + response = get("/auth/facebook/callback", {}, {'rack.session' => session, 'omniauth.auth' => {'info' => "fred"}}) + response = get $expected_redirect, {}, {'rack.session' => session} assert { $captures.size == 1 } assert { $captures.first == {:facebook => "user"} } $captures = [] session = {} session[WardenOmniAuth::SCOPE_KEY] = "user" - response = get("/auth/twitter/callback", {}, {'rack.session' => session, 'rack.auth' => {'info' => 'fred'}}) - response = get expected_redirect, {}, {'rack.session' => session} + response = get("/auth/twitter/callback", {}, {'rack.session' => session, 'omniauth.auth' => {'info' => 'fred'}}) + response = get $expected_redirect, {}, {'rack.session' => session} assert { $captures.size == 1 } assert { $captures.first == {:twitter => "user"} } $captures = [] session = {} session[WardenOmniAuth::SCOPE_KEY] = "user" - response = get("/auth/google_oauth2/callback", {}, {'rack.session' => session, 'rack.auth' => {'info' => 'fred'}}) - response = get expected_redirect, {}, {'rack.session' => session} + response = get("/auth/google_oauth2/callback", {}, {'rack.session' => session, 'omniauth.auth' => {'info' => 'fred'}}) + response = get $expected_redirect, {}, {'rack.session' => session} assert { $captures.size == 1 } assert { $captures.first == {:google_oauth2 => "user"} } ensure diff --git a/warden_omniauth.gemspec b/warden_omniauth.gemspec index 1e83396..52bbeb5 100644 --- a/warden_omniauth.gemspec +++ b/warden_omniauth.gemspec @@ -15,7 +15,7 @@ Gem::Specification.new do |s| s.rubyforge_project = "warden_omniauth" s.add_dependency "omniauth" - s.add_dependency "warden", ">=0.9" + s.add_dependency "warden", ">=1.0.0" s.add_development_dependency "bundler", ">= 1.0.0"