Permalink
Browse files

Merge pull request #90 from socialcast/client_credentials_basic_auth

Add client credentials grant type
  • Loading branch information...
2 parents b50d526 + 8fcbbcc commit bd3163c4cd4d317fa1ebfe405cabddbf08fcb893 Michael Bleigh committed Mar 26, 2012
View
@@ -26,7 +26,7 @@ the entire specification over time.
client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth2/callback')
# => "https://example.org/oauth/authorization?response_type=code&client_id=client_id&redirect_uri=http://localhost:8080/oauth2/callback"
- token = client.auth_code.get_token('authorization_code_value', :redirect_uri => 'http://localhost:8080/oauth2/callback')
+ token = client.auth_code.get_token('authorization_code_value', :redirect_uri => 'http://localhost:8080/oauth2/callback', :headers => {'Authorization' => 'Basic some_password'})
response = token.get('/api/resource', :params => { 'query_foo' => 'bar' })
response.class.name
# => OAuth2::Response
@@ -61,15 +61,22 @@ instance will be returned as usual and on 400+ status code responses, the
Response instance will contain the OAuth2::Error instance.
## <a name="authorization_grants"></a>Authorization Grants
-Currently the Authorization Code and Resource Owner Password Credentials
+Currently the Authorization Code, Resource Owner Password Credentials, and Client Credentials
authentication grant types have helper strategy classes that simplify client
-use. They are available via the #auth_code and #password methods respectively.
+use. They are available via the #auth_code, #password, and #client_credentials methods respectively.
auth_url = client.auth_code.authorize_url(:redirect_uri => 'http://localhost:8080/oauth/callback')
token = client.auth_code.get_token('code_value', :redirect_uri => 'http://localhost:8080/oauth/callback')
token = client.password.get_token('username', 'password')
+ token = client.client_credentials.get_token
+
+If you want to specify additional headers to be sent out with the
+request, add a 'headers' hash under 'params':
+
+ token = client.auth_code.get_token('code_value', :redirect_uri => 'http://localhost:8080/oauth/callback', :headers => {'Some' => 'Header'})
+
You can always use the #request method on the OAuth2::Client instance to make
requests for tokens for any Authentication grant type.
View
@@ -3,5 +3,6 @@
require 'oauth2/strategy/base'
require 'oauth2/strategy/auth_code'
require 'oauth2/strategy/password'
+require 'oauth2/strategy/client_credentials'
require 'oauth2/access_token'
require 'oauth2/response'
View
@@ -120,8 +120,10 @@ def request(verb, url, opts={})
def get_token(params, access_token_opts={})
opts = {:raise_errors => options[:raise_errors], :parse => params.delete(:parse)}
if options[:token_method] == :post
+ headers = params.delete(:headers)
opts[:body] = params
opts[:headers] = {'Content-Type' => 'application/x-www-form-urlencoded'}
+ opts[:headers].merge!(headers) if headers
else
opts[:params] = params
end
@@ -143,5 +145,12 @@ def auth_code
def password
@password ||= OAuth2::Strategy::Password.new(self)
end
+
+ # The Client Credentials strategy
+ #
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
+ def client_credentials
+ @client_credentials ||= OAuth2::Strategy::ClientCredentials.new(self)
+ end
end
end
@@ -0,0 +1,28 @@
+require 'httpauth'
+
+module OAuth2
+ module Strategy
+ # The Client Credentials Strategy
+ #
+ # @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.4
+ class ClientCredentials < Base
+ # Not used for this strategy
+ #
+ # @raise [NotImplementedError]
+ def authorize_url
+ raise NotImplementedError, "The authorization endpoint is not used in this strategy"
+ end
+
+ # Retrieve an access token given the specified client.
+ #
+ # @param [Hash] params additional params
+ # @param [Hash] opts options
+ def get_token(params={}, opts={})
+ request_body = opts.delete('auth_scheme') == 'request_body'
+ params.merge!('grant_type' => 'client_credentials')
+ params.merge!(request_body ? client_params : {:headers => {'Authorization' => HTTPAuth::Basic.pack_authorization(client_params['client_id'], client_params['client_secret'])}})
+ @client.get_token(params, opts.merge('refresh_token' => nil))
+ end
+ end
+ end
+end
View
@@ -4,6 +4,7 @@ require File.expand_path('../lib/oauth2/version', __FILE__)
Gem::Specification.new do |gem|
gem.add_dependency 'faraday', '~> 0.7'
gem.add_dependency 'multi_json', '~> 1.0'
+ gem.add_dependency 'httpauth', '~> 0.1'
gem.add_development_dependency 'multi_xml'
gem.add_development_dependency 'rake'
gem.add_development_dependency 'rdoc'
@@ -0,0 +1,70 @@
+require 'helper'
+
+describe OAuth2::Strategy::ClientCredentials do
+ let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout' }
+ let(:json_token) { '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}' }
+
+ let(:client) do
+ OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') do |builder|
+ builder.adapter :test do |stub|
+ stub.post('/oauth/token', {'grant_type' => 'client_credentials'}) do |env|
+ client_id, client_secret = HTTPAuth::Basic.unpack_authorization(env[:request_headers]['Authorization'])
+ client_id == 'abc' && client_secret == 'def' or raise Faraday::Adapter::Test::Stubs::NotFound.new
+ case @mode
+ when "formencoded"
+ [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
+ when "json"
+ [200, {'Content-Type' => 'application/json'}, json_token]
+ end
+ end
+ stub.post('/oauth/token', {'client_id' => 'abc', 'client_secret' => 'def', 'grant_type' => 'client_credentials'}) do |env|
+ case @mode
+ when "formencoded"
+ [200, {'Content-Type' => 'application/x-www-form-urlencoded'}, kvform_token]
+ when "json"
+ [200, {'Content-Type' => 'application/json'}, json_token]
+ end
+ end
+ end
+ end
+ end
+
+ subject {client.client_credentials}
+
+ describe "#authorize_url" do
+ it "should raise NotImplementedError" do
+ lambda {subject.authorize_url}.should raise_error(NotImplementedError)
+ end
+ end
+
+ %w(json formencoded).each do |mode|
+ %w(default basic_auth request_body).each do |auth_scheme|
+ describe "#get_token (#{mode}) (#{auth_scheme})" do
+ before do
+ @mode = mode
+ @access = subject.get_token({}, auth_scheme == 'default' ? {} : {'auth_scheme' => auth_scheme})
+ end
+
+ it 'returns AccessToken with same Client' do
+ @access.client.should == client
+ end
+
+ it 'returns AccessToken with #token' do
+ @access.token.should == 'salmon'
+ end
+
+ it 'returns AccessToken without #refresh_token' do
+ @access.refresh_token.should be_nil
+ end
+
+ it 'returns AccessToken with #expires_in' do
+ @access.expires_in.should == 600
+ end
+
+ it 'returns AccessToken with #expires_at' do
+ @access.expires_at.should_not be_nil
+ end
+ end
+ end
+ end
+end

0 comments on commit bd3163c

Please sign in to comment.