diff --git a/README.rdoc b/README.rdoc
index 2ceb889..9b8ea0a 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -13,10 +13,18 @@ oauth services you would like to have on the Warden::Manager middleware
config.oauth(:twitter) do |twitter|
twitter.consumer_secret =
twitter.consumer_key =
- twitter.options :site => 'http://twitter.com'
+ twitter.options({
+ :site => 'http://twitter.com',
+ :oauth_callback => (BASE_URL.end_with?('/') ? BASE_URL[0..-2] : BASE_URL) + '/users',
+ :oauth_callback_confirmed => 'true'
+ })
end
config.default_strategies(:twitter_oauth, :password, :other)
end
+
+You then need to set the BASE_URL constant in your environment files so that the oauth_callback will work:
+
+ BASE_URL = "http://localhost"
== Giving an Access Token fetcher
@@ -61,7 +69,34 @@ Note:
In Rails, don't set the :warden_oauth_provider parameter as part of the login route, if you do this, rails will catch the parameter, but not the
warden rack middleware, ergo, it won't work as expected.
+== Oauth 2.0
+To use oauth 2.0 it is very similar to standard oauth. Here is my example for working with facebook
+
+
+ config.warden do |manager|
+ manager.oauth2(:facebook) do |fb|
+ fb.consumer_key = FACEBOOK_CONSUMER_KEY
+ fb.consumer_secret = FACEBOOK_CONSUMER_SECRET
+ fb.client_id = FACEBOOK_CLIENT_ID
+ fb.options({
+ :authorize_url => 'https://www.facebook.com/dialog/oauth',
+ :access_token_url => 'https://graph.facebook.com/oauth/access_token',
+ :site => 'https://graph.facebook.com',
+ :scope => 'email,offline_access',
+ :redirect_uri => (BASE_URL.end_with?('/') ? BASE_URL[0..-2] : BASE_URL) + '/users'
+ })
+ end
+ manager.default_strategies(:scope => :user).unshift :facebook_oauth
+ end
+
+Note: You still need to give an Access Token fetcher
+
+ Warden::OAuth2.access_token_user_finder(:facebook) do |access_token|
+ end
+
+Note: Instead of oauth the keyword oauth2 is used in both examples above
+
== Examples
If you want to know how to make a twitter authentication client, check examples/twitter/application.rb
diff --git a/lib/warden_oauth.rb b/lib/warden_oauth.rb
index 4dceb36..63adb7b 100644
--- a/lib/warden_oauth.rb
+++ b/lib/warden_oauth.rb
@@ -18,3 +18,23 @@ module OAuth
end
end
+
+require "oauth2"
+
+module Warden
+ module OAuth2
+
+ base_path = File.dirname(__FILE__) + "/warden_oauth2"
+
+ require base_path + "/base"
+ require base_path + "/errors"
+ autoload :Utils, base_path + '/utils'
+ autoload :StrategyBuilder, base_path + '/strategy_builder'
+ autoload :Strategy, base_path + '/strategy'
+ autoload :Config, base_path + "/config"
+ require base_path + "/config_extension"
+
+
+ end
+end
+
diff --git a/lib/warden_oauth/strategy.rb b/lib/warden_oauth/strategy.rb
index c5be33c..d3077b9 100644
--- a/lib/warden_oauth/strategy.rb
+++ b/lib/warden_oauth/strategy.rb
@@ -43,7 +43,7 @@ def valid?
def authenticate!
if params.include?('warden_oauth_provider')
store_request_token_on_session
- redirect!(request_token.authorize_url)
+ redirect!(request_token.authorize_url(config.options))
throw(:warden)
elsif params.include?('oauth_token')
load_request_token_from_session
@@ -78,8 +78,7 @@ def consumer
end
def request_token
- host_with_port = Warden::OAuth::Utils.host_with_port(request)
- @request_token ||= consumer.get_request_token(:oauth_callback => host_with_port)
+ @request_token ||= consumer.get_request_token(:oauth_callback => config.options[:oauth_callback])
end
def access_token
diff --git a/lib/warden_oauth2/base.rb b/lib/warden_oauth2/base.rb
new file mode 100644
index 0000000..b38d641
--- /dev/null
+++ b/lib/warden_oauth2/base.rb
@@ -0,0 +1,13 @@
+module Warden
+ module OAuth2
+
+ def self.access_token_user_finder(key, &block)
+ Strategy.access_token_user_finders[key] = block
+ end
+
+ def self.clear_access_token_user_finders
+ Strategy.access_token_user_finders.clear
+ end
+
+ end
+end
diff --git a/lib/warden_oauth2/config.rb b/lib/warden_oauth2/config.rb
new file mode 100644
index 0000000..4d7258c
--- /dev/null
+++ b/lib/warden_oauth2/config.rb
@@ -0,0 +1,51 @@
+module Warden
+ module OAuth2
+
+ #
+ # Holds all the information of the OAuth service.
+ #
+ class Config
+ attr_accessor :provider_name
+
+ def consumer_key(key = nil)
+ unless key.nil?
+ @consumer_key = key
+ end
+ @consumer_key
+ end
+ alias_method :consumer_key=, :consumer_key
+
+ def consumer_secret(secret = nil)
+ unless secret.nil?
+ @consumer_secret = secret
+ end
+ @consumer_secret
+ end
+ alias_method :consumer_secret=, :consumer_secret
+
+ def client_id(client_id = nil)
+ unless client_id.nil?
+ @client_id = client_id
+ end
+ @client_id
+ end
+ alias_method :client_id=, :client_id
+
+ def options(options = nil)
+ unless options.nil?
+ @options = options
+ end
+ @options
+ end
+ alias_method :options=, :options
+
+ def check_requirements
+ if @consumer_key.nil? || @consumer_secret.nil? || @client_id.nil?
+ raise Warden::OAuth2::ConfigError.new("You need to specify the consumer key, consumer secret, and client id")
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/lib/warden_oauth2/config_extension.rb b/lib/warden_oauth2/config_extension.rb
new file mode 100644
index 0000000..c838d63
--- /dev/null
+++ b/lib/warden_oauth2/config_extension.rb
@@ -0,0 +1,45 @@
+module Warden
+ module OAuth2
+
+ #
+ # Holds all the extensions made to Warden::Config in order to create OAuth
+ # consumers.
+ #
+ module ConfigExtension
+
+ #
+ # Helps to setup a new OAuth client authentication, to get started you need to define
+ # a service name, and then on the block assign the different values required in order
+ # to boot the OAuth process.
+ # @param [Symbol] service An identifier of the OAuth service
+ #
+ # @example
+ # use Warden::Manager do |config|
+ # config.oauth(:twitter) do
+ # consumer_key ""
+ # consumer_secret ""
+ # options :site => 'http://twitter.com'
+ # end
+ # end
+ #
+ def oauth2(service, &block)
+ config = Warden::OAuth2::Config.new
+ if block_given?
+ if block.arity == 1
+ yield config
+ else
+ config.instance_eval(&block)
+ end
+ end
+ config.check_requirements
+ config.provider_name = service
+ Warden::OAuth2::Strategy.build(service, config)
+ end
+
+ end
+
+ end
+end
+
+Warden::Config.send(:include, Warden::OAuth2::ConfigExtension)
+
diff --git a/lib/warden_oauth2/errors.rb b/lib/warden_oauth2/errors.rb
new file mode 100644
index 0000000..736761f
--- /dev/null
+++ b/lib/warden_oauth2/errors.rb
@@ -0,0 +1,9 @@
+module Warden
+ module OAuth2
+
+ class ConfigError < ArgumentError; end
+ class ServiceAlreadyRegistered < StandardError; end
+ class AccessTokenFinderMissing < StandardError; end
+
+ end
+end
diff --git a/lib/warden_oauth2/strategy.rb b/lib/warden_oauth2/strategy.rb
new file mode 100644
index 0000000..71c33ce
--- /dev/null
+++ b/lib/warden_oauth2/strategy.rb
@@ -0,0 +1,144 @@
+module Warden
+ module OAuth2
+
+ #
+ # Holds all the main logic of the OAuth authentication, all the generated
+ # OAuth classes will extend from this class
+ #
+ class Strategy < Warden::Strategies::Base
+ extend StrategyBuilder
+
+ ######################
+ ### Strategy Logic ###
+ ######################
+
+
+ def self.access_token_user_finders
+ (@_user_token_finders ||= {})
+ end
+
+ #
+ # An OAuth strategy will be valid to execute if:
+ # * A 'warden_oauth_provider' parameter is given, with the name of the OAuth service
+ # * A 'oauth_token' is being receive on the request (response from an OAuth provider)
+ #
+ def valid?
+ (params.include?('warden_oauth2_provider') && params['warden_oauth2_provider'] == config.provider_name.to_s) ||
+ params.include?('code')
+ end
+
+
+ #
+ # Manages the OAuth authentication process, there can be 3 outcomes from this Strategy:
+ # 1. The OAuth credentials are invalid and the FailureApp is called
+ # 2. The OAuth credentials are valid, but there is no user associated to them. In this case
+ # the FailureApp is called, but the env['warden.options'][:oauth][:access_token] will be
+ # available.
+ # 3. The OAuth credentials are valid, and the user is authenticated successfuly
+ #
+ # @note
+ # If you want to signup users with the twitter credentials, you can manage the creation of a new
+ # user in the FailureApp with the given access_token
+ #
+ def authenticate!
+ puts "Inside OAUTH2 authenticate!****^^^^^^^^^^^^^^^^^^^"
+ if params.include?('warden_oauth2_provider')
+ #store_request_token_url_on_session
+ redirect!(consumer.authorize_url(:redirect_uri => config.options[:redirect_uri],:client_id => config.client_id,:scope => config.options[:scope]))
+ throw(:warden)
+ elsif params.include?('code')
+ #load_request_token_from_session
+ user = find_user_by_access_token(access_token)
+ if user.nil?
+ puts "got bad user"
+ fail!("User with access token not found")
+ throw_error_with_oauth_info
+ else
+ puts "Got success"
+ success!(user)
+ end
+ end
+ end
+
+ def fail!(msg) #:nodoc:
+ self.errors.add(service_param_name.to_sym, msg)
+ super
+ end
+
+ ###################
+ ### OAuth Logic ###
+ ###################
+
+ def consumer
+ puts 'options - ' + config.options.inspect
+ @consumer ||= ::OAuth2::Client.new(config.client_id, config.consumer_secret, config.options)
+ end
+
+ # def request_token
+ # #@request_token_url ||= consumer.get_request_token(:redirect_uri => config.options[:redirect_uri],:scope => config.options[:scope])
+ # @request_token_url ||= consumer.authorize_url
+ # end
+ #
+ def access_token
+ puts 'access_token_url = ' + consumer.access_token_url.inspect
+ @access_token ||= consumer.web_server.get_access_token(params['code'],:redirect_uri => config.options[:redirect_uri])
+ end
+
+ protected
+
+ def find_user_by_access_token(access_token)
+ raise RuntimeError.new(<<-ERROR_MESSAGE) unless self.respond_to?(:_find_user_by_access_token)
+
+You need to define a finder by access_token for this strategy.
+Write on the warden initializer the following code:
+Warden::OAuth2.access_token_user_finder(:#{config.provider_name}) do |access_token|
+ # Logic to get your user from an access_token
+end
+
+ERROR_MESSAGE
+ self._find_user_by_access_token(access_token)
+ end
+
+ def throw_error_with_oauth_info
+ throw(:warden, :oauth => {
+ self.config.provider_name => {
+ :provider => config.provider_name,
+ :access_token => access_token,
+ :consumer_key => config.consumer_key,
+ :consumer_secret => config.consumer_secret
+ }
+ })
+ end
+
+ # def store_request_token_on_session
+ # puts 'request_token - ' + request_token.inspect
+ # session[:request_token] = request_token.token
+ # session[:request_secret] = request_token.secret
+ # end
+ #
+ # def load_request_token_from_session
+ # token = session.delete(:request_token)
+ # secret = session.delete(:request_secret)
+ # @request_token = ::OAuth2::RequestToken.new(consumer, token, secret)
+ # end
+
+ # def missing_stored_token?
+ # !request_token
+ # end
+ #
+ # def stored_token_match_recieved_token?
+ # request_token.token == params['oauth_token']
+ # end
+
+ def service_param_name
+ '%s_oauth2' % config.provider_name
+ end
+
+ def config
+ self.class::CONFIG
+ end
+
+ end
+
+ end
+end
diff --git a/lib/warden_oauth2/strategy_builder.rb b/lib/warden_oauth2/strategy_builder.rb
new file mode 100644
index 0000000..8fe7745
--- /dev/null
+++ b/lib/warden_oauth2/strategy_builder.rb
@@ -0,0 +1,97 @@
+module Warden
+ module OAuth2
+
+ #
+ # Handles the creation an registration of OAuth strategies based on configuration parameters
+ # via the Warden::Manager.oauth method
+ #
+ module StrategyBuilder
+ extend self
+
+
+ #
+ # Defines the user finder from the access_token for the strategy, receives a block
+ # that will be invoked each time you want to find an user via an access_token in your
+ # system.
+ #
+ # @param blk Block that recieves the access_token as a parameter and will return a user or nil
+ #
+ def access_token_user_finder(&blk)
+ define_method(:_find_user_by_access_token, &blk)
+ end
+
+ #
+ # Manages the creation and registration of the OAuth strategy specified
+ # on the keyword
+ #
+ # @param [Symbol] name of the oauth service
+ # @param [Walruz::Config] configuration specified on the declaration of the oauth service
+ #
+ def build(keyword, config)
+ strategy_class = self.create_oauth_strategy_class(keyword)
+ self.register_oauth_strategy_class(keyword, strategy_class)
+ self.set_oauth_service_info(strategy_class, config)
+ # adding the access_token_user_finder to the strategy
+ if self.access_token_user_finders.include?(keyword)
+ strategy_class.access_token_user_finder(&self.access_token_user_finders[keyword])
+ end
+ end
+
+ #
+ # Creates the OAuth Strategy class from the keyword specified on the declaration of the
+ # oauth service. This class will be namespaced inside Warden::OAuth2::Strategy
+ #
+ # @param [Symbol] name of the OAuth service
+ # @return [Class] The class representing the Warden strategy
+ #
+ # @example
+ #
+ # self.create_oauth_strategy_class(:twitter) #=> Warden::OAuth2::Strategy::Twitter
+ # # will create a class Warden::OAuth2::Strategy::Twitter that extends from
+ # # Warden::OAuth2::Strategy
+ #
+ def create_oauth_strategy_class(keyword)
+ puts "making strategy class - " + keyword.inspect
+ class_name = Warden::OAuth2::Utils.camelize(keyword.to_s)
+ if self.const_defined?(class_name)
+ self.const_get(class_name)
+ else
+ self.const_set(class_name, Class.new(self))
+ end
+ end
+
+ #
+ # Registers the generated OAuth Strategy in the Warden::Strategies collection, the label
+ # of the strategy will be the given oauth service name plus an '_oauth' postfix
+ #
+ # @param [Symbol] name of the OAuth service
+ #
+ # @example
+ # manager.oauth(:twitter) { |twitter| ... } # will register a strategy :twitter_oauth
+ #
+ def register_oauth_strategy_class(keyword, strategy_class)
+ keyword_name = "%s_oauth" % keyword.to_s
+ if Warden::Strategies[keyword_name.to_sym].nil?
+ Warden::Strategies.add(keyword_name.to_sym, strategy_class)
+ end
+ end
+
+ #
+ # Defines a CONFIG constant in the generated class that will hold the configuration information
+ # (consumer_key, consumer_secret and options) of the oauth service.
+ #
+ # @param [Class] strategy class that will hold the configuration info
+ # @param [Warden::OAuth2::Config] configuration info of the oauth service
+ #
+ def set_oauth_service_info(strategy_class, config)
+ strategy_class.const_set("CONFIG", config) unless strategy_class.const_defined?("CONFIG")
+ end
+
+ protected :create_oauth_strategy_class,
+ :register_oauth_strategy_class,
+ :set_oauth_service_info
+
+ end
+
+ end
+end
diff --git a/lib/warden_oauth2/utils.rb b/lib/warden_oauth2/utils.rb
new file mode 100644
index 0000000..6208c7c
--- /dev/null
+++ b/lib/warden_oauth2/utils.rb
@@ -0,0 +1,40 @@
+module Warden
+ module OAuth2
+
+ #
+ # Contains methods from Rails to avoid unnecessary dependencies
+ #
+ module Utils
+
+ #
+ # Fetched from ActiveSupport::Inflector.camelize to avoid dependencies
+ #
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
+ if first_letter_in_uppercase
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
+ else
+ lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
+ end
+ end
+
+ #
+ # Fetched from ActionController::Request to avoid dependencies
+ #
+ def host_with_port(request)
+ url = request.scheme + "://"
+ url << request.host
+
+ if request.scheme == "https" && request.port != 443 ||
+ request.scheme == "http" && request.port != 80
+ url << ":#{request.port}"
+ end
+
+ url
+ end
+
+ module_function :camelize, :host_with_port
+
+ end
+
+ end
+end
diff --git a/warden_oauth.gemspec b/warden_oauth.gemspec
index 334c330..f8e08a3 100644
--- a/warden_oauth.gemspec
+++ b/warden_oauth.gemspec
@@ -36,6 +36,13 @@ Gem::Specification.new do |s|
"lib/warden_oauth/strategy.rb",
"lib/warden_oauth/strategy_builder.rb",
"lib/warden_oauth/utils.rb",
+ "lib/warden_oauth2/base.rb",
+ "lib/warden_oauth2/config.rb",
+ "lib/warden_oauth2/config_extension.rb",
+ "lib/warden_oauth2/errors.rb",
+ "lib/warden_oauth2/strategy.rb",
+ "lib/warden_oauth2/strategy_builder.rb",
+ "lib/warden_oauth2/utils.rb",
"spec/application_runner.rb",
"spec/application_scenario.rb",
"spec/fixtures/authorize_request_token.txt",
@@ -68,6 +75,7 @@ Gem::Specification.new do |s|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q, [">= 0.8.1"])
s.add_runtime_dependency(%q, [">= 0"])
+ s.add_runtime_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])
s.add_development_dependency(%q, [">= 0"])