Adding Bearer Token support #387

Merged
merged 9 commits into from Apr 18, 2013
View
19 lib/twitter/api/oauth.rb
@@ -6,6 +6,25 @@ module API
module OAuth
include Twitter::API::Utils
+ # Allows a registered application to obtain an OAuth 2 Bearer Token, which can be used to make API requests
+ # on an application's own behalf, without a user context.
+ #
+ # Only one bearer token may exist outstanding for an application, and repeated requests to this method
+ # will yield the same already-existent token until it has been invalidated.
+ #
+ # @see https://dev.twitter.com/docs/api/1.1/post/oauth2/token
+ # @rate_limited No
+ # @authentication Required
+ # @raise [Twitter::Error::Unauthorized] Error raised when supplied user credentials are not valid.
+ # @return [Twitter::Token] The Bearer Token. token_type should be 'bearer'.
+ # @example Generate a Bearer Token
+ # client = Twitter::Client.new :consumer_key => "abc", :consumer_secret => 'def'
+ # bearer_token = client.token
+ def token
+ object_from_response(Twitter::Token, :post, "/oauth2/token", :grant_type => "client_credentials", :bearer_token_request => true)
+ end
+ alias bearer_token token
+
# Allows a registered application to revoke an issued OAuth 2 Bearer Token by presenting its client credentials.
#
# @see https://dev.twitter.com/docs/api/1.1/post/oauth2/invalidate_token
View
44 lib/twitter/client.rb
@@ -20,6 +20,7 @@
require 'twitter/error/client_error'
require 'twitter/error/decode_error'
require 'simple_oauth'
+require 'base64'
require 'uri'
module Twitter
@@ -78,11 +79,33 @@ def put(path, params={})
end
private
+ # Returns a proc that can be used to setup the Faraday::Request headers
+ #
+ # @param method [Symbol]
+ # @param path [String]
+ # @param params [Hash]
+ # @return [Proc]
+ def request_setup(method, path, params)
+ if params.delete :bearer_token_request
+ Proc.new do |request|
+ request.headers[:authorization] = bearer_token_credentials_auth_header
+ request.headers[:content_type] = 'application/x-www-form-urlencoded; charset=UTF-8'
+ request.headers[:accept] = '*/*' # It is important we set this, otherwise we get an error.
+ end
+ elsif application_only_auth?
+ Proc.new do |request|
+ request.headers[:authorization] = bearer_auth_header
+ end
+ else
+ Proc.new do |request|
+ request.headers[:authorization] = oauth_auth_header(method, path, params).to_s
+ end
+ end
+ end
def request(method, path, params={}, signature_params=params)
- connection.send(method.to_sym, path, params) do |request|
- request.headers[:authorization] = auth_header(method.to_sym, path, signature_params).to_s
- end.env
+ request_setup = request_setup(method, path, params)
+ connection.send(method.to_sym, path, params, &request_setup).env
rescue Faraday::Error::ClientError
raise Twitter::Error::ClientError
rescue MultiJson::DecodeError
@@ -100,10 +123,21 @@ def connection
end
end
- def auth_header(method, path, params={})
+ # Generates authentication header for a bearer token request
+ #
+ # @return [String]
+ def bearer_token_credentials_auth_header
+ basic_auth_token = Base64.strict_encode64("#{@consumer_key}:#{@consumer_secret}")
+ "Basic #{basic_auth_token}"
+ end
+
+ def bearer_auth_header
+ "Bearer #{@bearer_token}"
+ end
+
+ def oauth_auth_header(method, path, params={})
uri = URI(@endpoint + path)
SimpleOAuth::Header.new(method, uri, params, credentials)
end
-
end
end
View
8 lib/twitter/configurable.rb
@@ -4,7 +4,7 @@
module Twitter
module Configurable
extend Forwardable
- attr_writer :consumer_key, :consumer_secret, :oauth_token, :oauth_token_secret
+ attr_writer :consumer_key, :consumer_secret, :oauth_token, :oauth_token_secret, :bearer_token
attr_accessor :endpoint, :connection_options, :identity_map, :middleware
def_delegator :options, :hash
@@ -16,6 +16,7 @@ def keys
:consumer_secret,
:oauth_token,
:oauth_token_secret,
+ :bearer_token,
:endpoint,
:connection_options,
:identity_map,
@@ -37,7 +38,7 @@ def configure
# @return [Boolean]
def credentials?
- credentials.values.all?
+ credentials.values.all? || @bearer_token
end
def reset!
@@ -49,6 +50,9 @@ def reset!
alias setup reset!
private
+ def application_only_auth?
+ !!@bearer_token
+ end
# @return [Hash]
def credentials
View
5 lib/twitter/default.rb
@@ -69,6 +69,11 @@ def oauth_token_secret
ENV['TWITTER_OAUTH_TOKEN_SECRET']
end
+ # @return [String]
+ def bearer_token
+ ENV['TWITTER_BEARER_TOKEN']
+ end
+
# @note This is configurable in case you want to use a Twitter-compatible endpoint.
# @see http://status.net/wiki/Twitter-compatible_API
# @see http://en.blog.wordpress.com/2009/12/12/twitter-api/
View
26 spec/twitter/api/oauth_spec.rb
@@ -3,7 +3,31 @@
describe Twitter::API::OAuth do
before do
- @client = Twitter::Client.new
+ @client = Twitter::Client.new :consumer_key => 'CK', :consumer_secret => 'CS'
+ end
+
+ describe "#token" do
+ before do
+ # WebMock treats Basic Auth differently so we have to chack against the full url with credentials.
+ @oauth2_token_url = "https://CK:CS@api.twitter.com/oauth2/token"
+ stub_request(:post, @oauth2_token_url).with(:body => "grant_type=client_credentials").to_return(:body => '{"token_type" : "bearer", "access_token" : "AAAA%2FAAA%3DAAAAAAAA"}', :headers => {:content_type => "application/json; charset=utf-8"})
+ end
+ it "requests the correct resource" do
+ @client.token
+ expect(a_request(:post, @oauth2_token_url).with(:body => {:grant_type => "client_credentials"})).to have_been_made
+ end
+ it "requests with the correct headers" do
+ @client.token
+ expect(a_request(:post, @oauth2_token_url).with(:headers => {
+ :content_type => "application/x-www-form-urlencoded; charset=UTF-8",
+ :accept => "*/*"
+ })).to have_been_made
+ end
+ it "returns the bearer token" do
+ token = @client.token
+ expect(token.access_token).to eq "AAAA%2FAAA%3DAAAAAAAA"
+ expect(token.token_type).to eq "bearer"
+ end
end
describe "#invalidate_token" do
View
24 spec/twitter/client_spec.rb
@@ -38,6 +38,7 @@
:middleware => Proc.new{},
:oauth_token => 'OT',
:oauth_token_secret => 'OS',
+ :bearer_token => 'BT',
:identity_map => ::Hash
}
end
@@ -139,10 +140,10 @@
end
end
- describe "#auth_header" do
+ describe "#oauth_auth_header" do
it "creates the correct auth headers" do
uri = "/1.1/direct_messages.json"
- authorization = subject.send(:auth_header, :get, uri)
+ authorization = subject.send(:oauth_auth_header, :get, uri)
expect(authorization.options[:signature_method]).to eq "HMAC-SHA1"
expect(authorization.options[:version]).to eq "1.0"
expect(authorization.options[:consumer_key]).to eq "CK"
@@ -152,4 +153,23 @@
end
end
+ describe "#bearer_auth_header" do
+ subject do
+ Twitter::Client.new(:bearer_token => "BT")
+ end
+
+ it "creates the correct auth headers with supplied bearer_token" do
+ uri = "/1.1/direct_messages.json"
+ authorization = subject.send(:bearer_auth_header)
+ expect(authorization).to eq "Bearer BT"
+ end
+ end
+
+ describe "#bearer_token_credentials_auth_header" do
+ it "creates the correct auth header with supplied consumer_key and consumer_secret" do
+ uri = "/1.1/direct_messages.json"
+ authorization = subject.send(:bearer_token_credentials_auth_header)
+ expect(authorization).to eq "Basic #{Base64.strict_encode64("CK:CS")}"
+ end
+ end
end
View
8 spec/twitter_spec.rb
@@ -91,7 +91,13 @@
end
describe ".credentials?" do
- it "returns true if all credentials are present" do
+ it "returns true if only bearer_token is supplied" do
+ Twitter.configure do |config|
+ config.bearer_token = 'BT'
+ end
+ expect(Twitter.credentials?).to be_true
+ end
+ it "returns true if all oauth credentials are present" do
Twitter.configure do |config|
config.consumer_key = 'CK'
config.consumer_secret = 'CS'