oauth2-1.0.0/ 0000755 0000041 0000041 00000000000 12363614141 012743 5 ustar www-data www-data oauth2-1.0.0/Rakefile 0000644 0000041 0000041 00000001576 12363614141 014421 0 ustar www-data www-data require 'bundler'
Bundler::GemHelper.install_tasks
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec)
task :test => :spec
namespace :doc do
require 'rdoc/task'
require File.expand_path('../lib/oauth2/version', __FILE__)
RDoc::Task.new do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = "oauth2 #{OAuth2::Version}"
rdoc.main = 'README.md'
rdoc.rdoc_files.include('README.md', 'LICENSE.md', 'lib/**/*.rb')
end
end
begin
require 'rubocop/rake_task'
RuboCop::RakeTask.new
rescue LoadError
task :rubocop do
$stderr.puts 'RuboCop is disabled'
end
end
require 'yardstick/rake/measurement'
Yardstick::Rake::Measurement.new do |measurement|
measurement.output = 'measurement/report.txt'
end
require 'yardstick/rake/verify'
Yardstick::Rake::Verify.new do |verify|
verify.threshold = 58.8
end
task :default => [:spec, :rubocop, :verify_measurements]
oauth2-1.0.0/oauth2.gemspec 0000644 0000041 0000041 00000002337 12363614141 015517 0 ustar www-data www-data # coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'oauth2/version'
Gem::Specification.new do |spec|
spec.add_dependency 'faraday', ['>= 0.8', '< 0.10']
spec.add_dependency 'jwt', '~> 1.0'
spec.add_dependency 'multi_json', '~> 1.3'
spec.add_dependency 'multi_xml', '~> 0.5'
spec.add_dependency 'rack', '~> 1.2'
spec.add_development_dependency 'bundler', '~> 1.0'
spec.authors = ['Michael Bleigh', 'Erik Michaels-Ober']
spec.description = 'A Ruby wrapper for the OAuth 2.0 protocol built with a similar style to the original OAuth spec.'
spec.email = ['michael@intridea.com', 'sferik@gmail.com']
spec.files = %w(.document CONTRIBUTING.md LICENSE.md README.md Rakefile oauth2.gemspec)
spec.files += Dir.glob('lib/**/*.rb')
spec.files += Dir.glob('spec/**/*')
spec.homepage = 'http://github.com/intridea/oauth2'
spec.licenses = %w(MIT)
spec.name = 'oauth2'
spec.require_paths = %w(lib)
spec.required_rubygems_version = '>= 1.3.5'
spec.summary = 'A Ruby wrapper for the OAuth 2.0 protocol.'
spec.test_files = Dir.glob('spec/**/*')
spec.version = OAuth2::Version
end
oauth2-1.0.0/spec/ 0000755 0000041 0000041 00000000000 12363614141 013675 5 ustar www-data www-data oauth2-1.0.0/spec/oauth2/ 0000755 0000041 0000041 00000000000 12363614141 015077 5 ustar www-data www-data oauth2-1.0.0/spec/oauth2/strategy/ 0000755 0000041 0000041 00000000000 12363614141 016741 5 ustar www-data www-data oauth2-1.0.0/spec/oauth2/strategy/client_credentials_spec.rb 0000644 0000041 0000041 00000005451 12363614141 024140 0 ustar www-data www-data 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 = Base64.decode64(env[:request_headers]['Authorization'].split(' ', 2)[1]).split(':', 2)
client_id == 'abc' && client_secret == 'def' || fail(Faraday::Adapter::Test::Stubs::NotFound)
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 'raises NotImplementedError' do
expect { subject.authorize_url }.to raise_error(NotImplementedError)
end
end
describe '#authorization' do
it 'generates an Authorization header value for HTTP Basic Authentication' do
[
['abc', 'def', 'Basic YWJjOmRlZg=='],
['xxx', 'secret', 'Basic eHh4OnNlY3JldA==']
].each do |client_id, client_secret, expected|
expect(subject.authorization(client_id, client_secret)).to eq(expected)
end
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
expect(@access.client).to eq(client)
end
it 'returns AccessToken with #token' do
expect(@access.token).to eq('salmon')
end
it 'returns AccessToken without #refresh_token' do
expect(@access.refresh_token).to be_nil
end
it 'returns AccessToken with #expires_in' do
expect(@access.expires_in).to eq(600)
end
it 'returns AccessToken with #expires_at' do
expect(@access.expires_at).not_to be_nil
end
end
end
end
end
oauth2-1.0.0/spec/oauth2/strategy/implicit_spec.rb 0000644 0000041 0000041 00000001431 12363614141 022111 0 ustar www-data www-data require 'helper'
describe OAuth2::Strategy::Implicit do
let(:client) { OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') }
subject { client.implicit }
describe '#authorize_url' do
it 'includes the client_id' do
expect(subject.authorize_url).to include('client_id=abc')
end
it 'includes the type' do
expect(subject.authorize_url).to include('response_type=token')
end
it 'includes passed in options' do
cb = 'http://myserver.local/oauth/callback'
expect(subject.authorize_url(:redirect_uri => cb)).to include("redirect_uri=#{Rack::Utils.escape(cb)}")
end
end
describe '#get_token' do
it 'raises NotImplementedError' do
expect { subject.get_token }.to raise_error(NotImplementedError)
end
end
end
oauth2-1.0.0/spec/oauth2/strategy/auth_code_spec.rb 0000644 0000041 0000041 00000006043 12363614141 022236 0 ustar www-data www-data require 'helper'
describe OAuth2::Strategy::AuthCode do
let(:code) { 'sushi' }
let(:kvform_token) { 'expires_in=600&access_token=salmon&refresh_token=trout&extra_param=steve' }
let(:facebook_token) { kvform_token.gsub('_in', '') }
let(:json_token) { MultiJson.encode(:expires_in => 600, :access_token => 'salmon', :refresh_token => 'trout', :extra_param => 'steve') }
let(:client) do
OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com') do |builder|
builder.adapter :test do |stub|
stub.get("/oauth/token?client_id=abc&client_secret=def&code=#{code}&grant_type=authorization_code") 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]
when 'from_facebook'
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token]
end
end
stub.post('/oauth/token', 'client_id' => 'abc', 'client_secret' => 'def', 'code' => 'sushi', 'grant_type' => 'authorization_code') 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]
when 'from_facebook'
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, facebook_token]
end
end
end
end
end
subject { client.auth_code }
describe '#authorize_url' do
it 'includes the client_id' do
expect(subject.authorize_url).to include('client_id=abc')
end
it 'includes the type' do
expect(subject.authorize_url).to include('response_type=code')
end
it 'includes passed in options' do
cb = 'http://myserver.local/oauth/callback'
expect(subject.authorize_url(:redirect_uri => cb)).to include("redirect_uri=#{Rack::Utils.escape(cb)}")
end
end
%w(json formencoded from_facebook).each do |mode|
[:get, :post].each do |verb|
describe "#get_token (#{mode}, access_token_method=#{verb}" do
before do
@mode = mode
client.options[:token_method] = verb
@access = subject.get_token(code)
end
it 'returns AccessToken with same Client' do
expect(@access.client).to eq(client)
end
it 'returns AccessToken with #token' do
expect(@access.token).to eq('salmon')
end
it 'returns AccessToken with #refresh_token' do
expect(@access.refresh_token).to eq('trout')
end
it 'returns AccessToken with #expires_in' do
expect(@access.expires_in).to eq(600)
end
it 'returns AccessToken with #expires_at' do
expect(@access.expires_at).to be_kind_of(Integer)
end
it 'returns AccessToken with params accessible via []' do
expect(@access['extra_param']).to eq('steve')
end
end
end
end
end
oauth2-1.0.0/spec/oauth2/strategy/assertion_spec.rb 0000644 0000041 0000041 00000002741 12363614141 022313 0 ustar www-data www-data require 'helper'
describe OAuth2::Strategy::Assertion do
let(:client) do
cli = OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com')
cli.connection.build do |b|
b.adapter :test do |stub|
stub.post('/oauth/token') do |env|
case @mode
when 'formencoded'
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout']
when 'json'
[200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}']
end
end
end
end
cli
end
let(:params) { {:hmac_secret => 'foo'} }
subject { client.assertion }
describe '#authorize_url' do
it 'raises NotImplementedError' do
expect { subject.authorize_url }.to raise_error(NotImplementedError)
end
end
%w(json formencoded).each do |mode|
describe "#get_token (#{mode})" do
before do
@mode = mode
@access = subject.get_token(params)
end
it 'returns AccessToken with same Client' do
expect(@access.client).to eq(client)
end
it 'returns AccessToken with #token' do
expect(@access.token).to eq('salmon')
end
it 'returns AccessToken with #expires_in' do
expect(@access.expires_in).to eq(600)
end
it 'returns AccessToken with #expires_at' do
expect(@access.expires_at).not_to be_nil
end
end
end
end
oauth2-1.0.0/spec/oauth2/strategy/base_spec.rb 0000644 0000041 0000041 00000000302 12363614141 021205 0 ustar www-data www-data require 'helper'
describe OAuth2::Strategy::Base do
it 'initializes with a Client' do
expect { OAuth2::Strategy::Base.new(OAuth2::Client.new('abc', 'def')) }.not_to raise_error
end
end
oauth2-1.0.0/spec/oauth2/strategy/password_spec.rb 0000644 0000041 0000041 00000003070 12363614141 022142 0 ustar www-data www-data require 'helper'
describe OAuth2::Strategy::Password do
let(:client) do
cli = OAuth2::Client.new('abc', 'def', :site => 'http://api.example.com')
cli.connection.build do |b|
b.adapter :test do |stub|
stub.post('/oauth/token') do |env|
case @mode
when 'formencoded'
[200, {'Content-Type' => 'application/x-www-form-urlencoded'}, 'expires_in=600&access_token=salmon&refresh_token=trout']
when 'json'
[200, {'Content-Type' => 'application/json'}, '{"expires_in":600,"access_token":"salmon","refresh_token":"trout"}']
end
end
end
end
cli
end
subject { client.password }
describe '#authorize_url' do
it 'raises NotImplementedError' do
expect { subject.authorize_url }.to raise_error(NotImplementedError)
end
end
%w(json formencoded).each do |mode|
describe "#get_token (#{mode})" do
before do
@mode = mode
@access = subject.get_token('username', 'password')
end
it 'returns AccessToken with same Client' do
expect(@access.client).to eq(client)
end
it 'returns AccessToken with #token' do
expect(@access.token).to eq('salmon')
end
it 'returns AccessToken with #refresh_token' do
expect(@access.refresh_token).to eq('trout')
end
it 'returns AccessToken with #expires_in' do
expect(@access.expires_in).to eq(600)
end
it 'returns AccessToken with #expires_at' do
expect(@access.expires_at).not_to be_nil
end
end
end
end
oauth2-1.0.0/spec/oauth2/mac_token_spec.rb 0000644 0000041 0000041 00000006746 12363614141 020413 0 ustar www-data www-data require 'helper'
describe MACToken do
let(:token) { 'monkey' }
let(:client) do
Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
builder.request :url_encoded
builder.adapter :test do |stub|
VERBS.each do |verb|
stub.send(verb, '/token/header') { |env| [200, {}, env[:request_headers]['Authorization']] }
end
end
end
end
subject { MACToken.new(client, token, 'abc123') }
describe '#initialize' do
it 'assigns client and token' do
expect(subject.client).to eq(client)
expect(subject.token).to eq(token)
end
it 'assigns secret' do
expect(subject.secret).to eq('abc123')
end
it 'defaults algorithm to hmac-sha-256' do
expect(subject.algorithm).to be_instance_of(OpenSSL::Digest::SHA256)
end
it 'handles hmac-sha-256' do
mac = MACToken.new(client, token, 'abc123', :algorithm => 'hmac-sha-256')
expect(mac.algorithm).to be_instance_of(OpenSSL::Digest::SHA256)
end
it 'handles hmac-sha-1' do
mac = MACToken.new(client, token, 'abc123', :algorithm => 'hmac-sha-1')
expect(mac.algorithm).to be_instance_of(OpenSSL::Digest::SHA1)
end
it 'raises on improper algorithm' do
expect { MACToken.new(client, token, 'abc123', :algorithm => 'invalid-sha') }.to raise_error(ArgumentError)
end
end
describe '#request' do
VERBS.each do |verb|
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
expect(subject.post('/token/header').body).to include("MAC id=\"#{token}\"")
end
end
end
describe '#header' do
it 'does not generate the same header twice' do
header = subject.header('get', 'https://www.example.com/hello')
duplicate_header = subject.header('get', 'https://www.example.com/hello')
expect(header).to_not eq(duplicate_header)
end
it 'generates the proper format' do
header = subject.header('get', 'https://www.example.com/hello?a=1')
expect(header).to match(/MAC id="#{token}", ts="[0-9]+", nonce="[^"]+", mac="[^"]+"/)
end
it 'passes ArgumentError with an invalid url' do
expect { subject.header('get', 'this-is-not-valid') }.to raise_error(ArgumentError)
end
it 'passes URI::InvalidURIError through' do
expect { subject.header('get', nil) }.to raise_error(URI::InvalidURIError)
end
end
describe '#signature' do
it 'generates properly' do
signature = subject.signature(0, 'random-string', 'get', URI('https://www.google.com'))
expect(signature).to eq('rMDjVA3VJj3v1OmxM29QQljKia6msl5rjN83x3bZmi8=')
end
end
describe '#headers' do
it 'is an empty hash' do
expect(subject.headers).to eq({})
end
end
describe '.from_access_token' do
let(:access_token) do
AccessToken.new(
client, token,
:expires_at => 1,
:expires_in => 1,
:refresh_token => 'abc',
:random => 1
)
end
subject { MACToken.from_access_token(access_token, 'hello') }
it 'initializes client, token, and secret properly' do
expect(subject.client).to eq(client)
expect(subject.token).to eq(token)
expect(subject.secret).to eq('hello')
end
it 'initializes configuration options' do
expect(subject.expires_at).to eq(1)
expect(subject.expires_in).to eq(1)
expect(subject.refresh_token).to eq('abc')
end
it 'initializes params' do
expect(subject.params).to eq(:random => 1)
end
end
end
oauth2-1.0.0/spec/oauth2/client_spec.rb 0000644 0000041 0000041 00000017656 12363614141 017733 0 ustar www-data www-data require 'helper'
describe OAuth2::Client do
let!(:error_value) { 'invalid_token' }
let!(:error_description_value) { 'bad bad token' }
subject do
OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
builder.adapter :test do |stub|
stub.get('/success') { |env| [200, {'Content-Type' => 'text/awesome'}, 'yay'] }
stub.get('/reflect') { |env| [200, {}, env[:body]] }
stub.post('/reflect') { |env| [200, {}, env[:body]] }
stub.get('/unauthorized') { |env| [401, {'Content-Type' => 'application/json'}, MultiJson.encode(:error => error_value, :error_description => error_description_value)] }
stub.get('/conflict') { |env| [409, {'Content-Type' => 'text/plain'}, 'not authorized'] }
stub.get('/redirect') { |env| [302, {'Content-Type' => 'text/plain', 'location' => '/success'}, ''] }
stub.post('/redirect') { |env| [303, {'Content-Type' => 'text/plain', 'location' => '/reflect'}, ''] }
stub.get('/error') { |env| [500, {'Content-Type' => 'text/plain'}, 'unknown error'] }
stub.get('/empty_get') { |env| [204, {}, nil] }
end
end
end
describe '#initialize' do
it 'assigns id and secret' do
expect(subject.id).to eq('abc')
expect(subject.secret).to eq('def')
end
it 'assigns site from the options hash' do
expect(subject.site).to eq('https://api.example.com')
end
it 'assigns Faraday::Connection#host' do
expect(subject.connection.host).to eq('api.example.com')
end
it 'leaves Faraday::Connection#ssl unset' do
expect(subject.connection.ssl).to be_empty
end
it 'is able to pass a block to configure the connection' do
connection = double('connection')
builder = double('builder')
allow(connection).to receive(:build).and_yield(builder)
allow(Faraday::Connection).to receive(:new).and_return(connection)
expect(builder).to receive(:adapter).with(:test)
OAuth2::Client.new('abc', 'def') do |client|
client.adapter :test
end.connection
end
it 'defaults raise_errors to true' do
expect(subject.options[:raise_errors]).to be true
end
it 'allows true/false for raise_errors option' do
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => false)
expect(client.options[:raise_errors]).to be false
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => true)
expect(client.options[:raise_errors]).to be true
end
it 'allows override of raise_errors option' do
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :raise_errors => true) do |builder|
builder.adapter :test do |stub|
stub.get('/notfound') { |env| [404, {}, nil] }
end
end
expect(client.options[:raise_errors]).to be true
expect { client.request(:get, '/notfound') }.to raise_error(OAuth2::Error)
response = client.request(:get, '/notfound', :raise_errors => false)
expect(response.status).to eq(404)
end
it 'allows get/post for access_token_method option' do
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :access_token_method => :get)
expect(client.options[:access_token_method]).to eq(:get)
client = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :access_token_method => :post)
expect(client.options[:access_token_method]).to eq(:post)
end
it 'does not mutate the opts hash argument' do
opts = {:site => 'http://example.com/'}
opts2 = opts.dup
OAuth2::Client.new 'abc', 'def', opts
expect(opts).to eq(opts2)
end
end
%w(authorize token).each do |url_type|
describe ":#{url_type}_url option" do
it "defaults to a path of /oauth/#{url_type}" do
expect(subject.send("#{url_type}_url")).to eq("https://api.example.com/oauth/#{url_type}")
end
it "is settable via the :#{url_type}_url option" do
subject.options[:"#{url_type}_url"] = '/oauth/custom'
expect(subject.send("#{url_type}_url")).to eq('https://api.example.com/oauth/custom')
end
it 'allows a different host than the site' do
subject.options[:"#{url_type}_url"] = 'https://api.foo.com/oauth/custom'
expect(subject.send("#{url_type}_url")).to eq('https://api.foo.com/oauth/custom')
end
end
end
describe '#request' do
it 'works with a null response body' do
expect(subject.request(:get, 'empty_get').body).to eq('')
end
it 'returns on a successful response' do
response = subject.request(:get, '/success')
expect(response.body).to eq('yay')
expect(response.status).to eq(200)
expect(response.headers).to eq('Content-Type' => 'text/awesome')
end
it 'outputs to $stdout when OAUTH_DEBUG=true' do
allow(ENV).to receive(:[]).with('http_proxy').and_return(nil)
allow(ENV).to receive(:[]).with('OAUTH_DEBUG').and_return('true')
output = capture_output do
subject.request(:get, '/success')
end
expect(output).to include 'INFO -- : get https://api.example.com/success', 'INFO -- : get https://api.example.com/success'
end
it 'posts a body' do
response = subject.request(:post, '/reflect', :body => 'foo=bar')
expect(response.body).to eq('foo=bar')
end
it 'follows redirects properly' do
response = subject.request(:get, '/redirect')
expect(response.body).to eq('yay')
expect(response.status).to eq(200)
expect(response.headers).to eq('Content-Type' => 'text/awesome')
end
it 'redirects using GET on a 303' do
response = subject.request(:post, '/redirect', :body => 'foo=bar')
expect(response.body).to be_empty
expect(response.status).to eq(200)
end
it 'obeys the :max_redirects option' do
max_redirects = subject.options[:max_redirects]
subject.options[:max_redirects] = 0
response = subject.request(:get, '/redirect')
expect(response.status).to eq(302)
subject.options[:max_redirects] = max_redirects
end
it 'returns if raise_errors is false' do
subject.options[:raise_errors] = false
response = subject.request(:get, '/unauthorized')
expect(response.status).to eq(401)
expect(response.headers).to eq('Content-Type' => 'application/json')
expect(response.error).not_to be_nil
end
%w(/unauthorized /conflict /error).each do |error_path|
it "raises OAuth2::Error on error response to path #{error_path}" do
expect { subject.request(:get, error_path) }.to raise_error(OAuth2::Error)
end
end
it 'parses OAuth2 standard error response' do
begin
subject.request(:get, '/unauthorized')
rescue StandardError => e
expect(e.code).to eq(error_value)
expect(e.description).to eq(error_description_value)
expect(e.to_s).to match(/#{error_value}/)
expect(e.to_s).to match(/#{error_description_value}/)
end
end
it 'provides the response in the Exception' do
begin
subject.request(:get, '/error')
rescue StandardError => e
expect(e.response).not_to be_nil
expect(e.to_s).to match(/unknown error/)
end
end
end
it 'instantiates an AuthCode strategy with this client' do
expect(subject.auth_code).to be_kind_of(OAuth2::Strategy::AuthCode)
end
it 'instantiates an Implicit strategy with this client' do
expect(subject.implicit).to be_kind_of(OAuth2::Strategy::Implicit)
end
context 'with SSL options' do
subject do
cli = OAuth2::Client.new('abc', 'def', :site => 'https://api.example.com', :ssl => {:ca_file => 'foo.pem'})
cli.connection.build do |b|
b.adapter :test
end
cli
end
it 'passes the SSL options along to Faraday::Connection#ssl' do
expect(subject.connection.ssl.fetch(:ca_file)).to eq('foo.pem')
end
end
end
oauth2-1.0.0/spec/oauth2/response_spec.rb 0000644 0000041 0000041 00000006103 12363614141 020274 0 ustar www-data www-data require 'helper'
describe OAuth2::Response do
describe '#initialize' do
let(:status) { 200 }
let(:headers) { {'foo' => 'bar'} }
let(:body) { 'foo' }
it 'returns the status, headers and body' do
response = double('response', :headers => headers,
:status => status,
:body => body)
subject = Response.new(response)
expect(subject.headers).to eq(headers)
expect(subject.status).to eq(status)
expect(subject.body).to eq(body)
end
end
describe '.register_parser' do
let(:response) do
double('response', :headers => {'Content-Type' => 'application/foo-bar'},
:status => 200,
:body => 'baz')
end
before do
OAuth2::Response.register_parser(:foobar, 'application/foo-bar') do |body|
"foobar #{body}"
end
end
it 'adds to the content types and parsers' do
expect(OAuth2::Response::PARSERS.keys).to include(:foobar)
expect(OAuth2::Response::CONTENT_TYPES.keys).to include('application/foo-bar')
end
it 'is able to parse that content type automatically' do
expect(OAuth2::Response.new(response).parsed).to eq('foobar baz')
end
end
describe '#parsed' do
it 'parses application/x-www-form-urlencoded body' do
headers = {'Content-Type' => 'application/x-www-form-urlencoded'}
body = 'foo=bar&answer=42'
response = double('response', :headers => headers, :body => body)
subject = Response.new(response)
expect(subject.parsed.keys.size).to eq(2)
expect(subject.parsed['foo']).to eq('bar')
expect(subject.parsed['answer']).to eq('42')
end
it 'parses application/json body' do
headers = {'Content-Type' => 'application/json'}
body = MultiJson.encode(:foo => 'bar', :answer => 42)
response = double('response', :headers => headers, :body => body)
subject = Response.new(response)
expect(subject.parsed.keys.size).to eq(2)
expect(subject.parsed['foo']).to eq('bar')
expect(subject.parsed['answer']).to eq(42)
end
it "doesn't try to parse other content-types" do
headers = {'Content-Type' => 'text/html'}
body = '
'
response = double('response', :headers => headers, :body => body)
expect(MultiJson).not_to receive(:decode)
expect(MultiJson).not_to receive(:load)
expect(Rack::Utils).not_to receive(:parse_query)
subject = Response.new(response)
expect(subject.parsed).to be_nil
end
end
context 'xml parser registration' do
it 'tries to load multi_xml and use it' do
expect(OAuth2::Response::PARSERS[:xml]).not_to be_nil
end
it 'is able to parse xml' do
headers = {'Content-Type' => 'text/xml'}
body = 'baz'
response = double('response', :headers => headers, :body => body)
expect(OAuth2::Response.new(response).parsed).to eq('foo' => {'bar' => 'baz'})
end
end
end
oauth2-1.0.0/spec/oauth2/access_token_spec.rb 0000644 0000041 0000041 00000013377 12363614141 021112 0 ustar www-data www-data require 'helper'
describe AccessToken do
let(:token) { 'monkey' }
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => 'refresh_bar') }
let(:client) do
Client.new('abc', 'def', :site => 'https://api.example.com') do |builder|
builder.request :url_encoded
builder.adapter :test do |stub|
VERBS.each do |verb|
stub.send(verb, '/token/header') { |env| [200, {}, env[:request_headers]['Authorization']] }
stub.send(verb, "/token/query?access_token=#{token}") { |env| [200, {}, Addressable::URI.parse(env[:url]).query_values['access_token']] }
stub.send(verb, '/token/body') { |env| [200, {}, env[:body]] }
end
stub.post('/oauth/token') { |env| [200, {'Content-Type' => 'application/json'}, refresh_body] }
end
end
end
subject { AccessToken.new(client, token) }
describe '#initialize' do
it 'assigns client and token' do
expect(subject.client).to eq(client)
expect(subject.token).to eq(token)
end
it 'assigns extra params' do
target = AccessToken.new(client, token, 'foo' => 'bar')
expect(target.params).to include('foo')
expect(target.params['foo']).to eq('bar')
end
def assert_initialized_token(target)
expect(target.token).to eq(token)
expect(target).to be_expires
expect(target.params.keys).to include('foo')
expect(target.params['foo']).to eq('bar')
end
it 'initializes with a Hash' do
hash = {:access_token => token, :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
target = AccessToken.from_hash(client, hash)
assert_initialized_token(target)
end
it 'initalizes with a form-urlencoded key/value string' do
kvform = "access_token=#{token}&expires_at=#{Time.now.to_i + 200}&foo=bar"
target = AccessToken.from_kvform(client, kvform)
assert_initialized_token(target)
end
it 'sets options' do
target = AccessToken.new(client, token, :param_name => 'foo', :header_format => 'Bearer %', :mode => :body)
expect(target.options[:param_name]).to eq('foo')
expect(target.options[:header_format]).to eq('Bearer %')
expect(target.options[:mode]).to eq(:body)
end
it 'initializes with a string expires_at' do
hash = {:access_token => token, :expires_at => '1361396829', 'foo' => 'bar'}
target = AccessToken.from_hash(client, hash)
assert_initialized_token(target)
expect(target.expires_at).to be_a(Integer)
end
end
describe '#request' do
context ':mode => :header' do
before do
subject.options[:mode] = :header
end
VERBS.each do |verb|
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
expect(subject.post('/token/header').body).to include(token)
end
end
end
context ':mode => :query' do
before do
subject.options[:mode] = :query
end
VERBS.each do |verb|
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
expect(subject.post('/token/query').body).to eq(token)
end
end
end
context ':mode => :body' do
before do
subject.options[:mode] = :body
end
VERBS.each do |verb|
it "sends the token in the Authorization header for a #{verb.to_s.upcase} request" do
expect(subject.post('/token/body').body.split('=').last).to eq(token)
end
end
end
end
describe '#expires?' do
it 'is false if there is no expires_at' do
expect(AccessToken.new(client, token)).not_to be_expires
end
it 'is true if there is an expires_in' do
expect(AccessToken.new(client, token, :refresh_token => 'abaca', :expires_in => 600)).to be_expires
end
it 'is true if there is an expires_at' do
expect(AccessToken.new(client, token, :refresh_token => 'abaca', :expires_in => Time.now.getutc.to_i + 600)).to be_expires
end
end
describe '#expired?' do
it 'is false if there is no expires_in or expires_at' do
expect(AccessToken.new(client, token)).not_to be_expired
end
it 'is false if expires_in is in the future' do
expect(AccessToken.new(client, token, :refresh_token => 'abaca', :expires_in => 10_800)).not_to be_expired
end
it 'is true if expires_at is in the past' do
access = AccessToken.new(client, token, :refresh_token => 'abaca', :expires_in => 600)
@now = Time.now + 10_800
allow(Time).to receive(:now).and_return(@now)
expect(access).to be_expired
end
end
describe '#refresh!' do
let(:access) do
AccessToken.new(client, token, :refresh_token => 'abaca',
:expires_in => 600,
:param_name => 'o_param')
end
it 'returns a refresh token with appropriate values carried over' do
refreshed = access.refresh!
expect(access.client).to eq(refreshed.client)
expect(access.options[:param_name]).to eq(refreshed.options[:param_name])
end
context 'with a nil refresh_token in the response' do
let(:refresh_body) { MultiJson.encode(:access_token => 'refreshed_foo', :expires_in => 600, :refresh_token => nil) }
it 'copies the refresh_token from the original token' do
refreshed = access.refresh!
expect(refreshed.refresh_token).to eq(access.refresh_token)
end
end
end
describe '#to_hash' do
it 'return a hash equals to the hash used to initialize access token' do
hash = {:access_token => token, :refresh_token => 'foobar', :expires_at => Time.now.to_i + 200, 'foo' => 'bar'}
access_token = AccessToken.from_hash(client, hash.clone)
expect(access_token.to_hash).to eq(hash)
end
end
end
oauth2-1.0.0/spec/helper.rb 0000644 0000041 0000041 00000001304 12363614141 015477 0 ustar www-data www-data require 'simplecov'
require 'coveralls'
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
SimpleCov::Formatter::HTMLFormatter,
Coveralls::SimpleCov::Formatter
]
SimpleCov.start do
add_filter '/spec/'
minimum_coverage(95.33)
end
require 'oauth2'
require 'addressable/uri'
require 'rspec'
RSpec.configure do |config|
config.expect_with :rspec do |c|
c.syntax = :expect
end
end
Faraday.default_adapter = :test
RSpec.configure do |conf|
include OAuth2
end
def capture_output(&block)
begin
old_stdout = $stdout
$stdout = StringIO.new
block.call
result = $stdout.string
ensure
$stdout = old_stdout
end
result
end
VERBS = [:get, :post, :put, :delete]
oauth2-1.0.0/LICENSE.md 0000644 0000041 0000041 00000002072 12363614141 014350 0 ustar www-data www-data Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
oauth2-1.0.0/lib/ 0000755 0000041 0000041 00000000000 12363614141 013511 5 ustar www-data www-data oauth2-1.0.0/lib/oauth2/ 0000755 0000041 0000041 00000000000 12363614141 014713 5 ustar www-data www-data oauth2-1.0.0/lib/oauth2/mac_token.rb 0000644 0000041 0000041 00000007701 12363614141 017205 0 ustar www-data www-data require 'base64'
require 'digest'
require 'openssl'
require 'securerandom'
module OAuth2
class MACToken < AccessToken
# Generates a MACToken from an AccessToken and secret
#
# @param [AccessToken] token the OAuth2::Token instance
# @option [String] secret the secret key value
# @param [Hash] opts the options to create the Access Token with
# @see MACToken#initialize
def self.from_access_token(token, secret, options = {})
new(token.client, token.token, secret, token.params.merge(
:refresh_token => token.refresh_token,
:expires_in => token.expires_in,
:expires_at => token.expires_at
).merge(options))
end
attr_reader :secret, :algorithm
# Initalize a MACToken
#
# @param [Client] client the OAuth2::Client instance
# @param [String] token the Access Token value
# @option [String] secret the secret key value
# @param [Hash] opts the options to create the Access Token with
# @option opts [String] :refresh_token (nil) the refresh_token value
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
# @option opts [FixNum, String] :algorithm (hmac-sha-256) the algorithm to use for the HMAC digest (one of 'hmac-sha-256', 'hmac-sha-1')
def initialize(client, token, secret, opts = {})
@secret = secret
self.algorithm = opts.delete(:algorithm) || 'hmac-sha-256'
super(client, token, opts)
end
# Make a request with the MAC Token
#
# @param [Symbol] verb the HTTP request method
# @param [String] path the HTTP URL path of the request
# @param [Hash] opts the options to make the request with
# @see Client#request
def request(verb, path, opts = {}, &block)
url = client.connection.build_url(path, opts[:params]).to_s
opts[:headers] ||= {}
opts[:headers].merge!('Authorization' => header(verb, url))
@client.request(verb, path, opts, &block)
end
# Get the headers hash (always an empty hash)
def headers
{}
end
# Generate the MAC header
#
# @param [Symbol] verb the HTTP request method
# @param [String] url the HTTP URL path of the request
def header(verb, url)
timestamp = Time.now.utc.to_i
nonce = Digest::MD5.hexdigest([timestamp, SecureRandom.hex].join(':'))
uri = URI.parse(url)
fail(ArgumentError, "could not parse \"#{url}\" into URI") unless uri.is_a?(URI::HTTP)
mac = signature(timestamp, nonce, verb, uri)
"MAC id=\"#{token}\", ts=\"#{timestamp}\", nonce=\"#{nonce}\", mac=\"#{mac}\""
end
# Generate the Base64-encoded HMAC digest signature
#
# @param [Fixnum] timestamp the timestamp of the request in seconds since epoch
# @param [String] nonce the MAC header nonce
# @param [Symbol] verb the HTTP request method
# @param [String] url the HTTP URL path of the request
def signature(timestamp, nonce, verb, uri)
signature = [
timestamp,
nonce,
verb.to_s.upcase,
uri.request_uri,
uri.host,
uri.port,
'', nil
].join("\n")
strict_encode64(OpenSSL::HMAC.digest(@algorithm, secret, signature))
end
# Set the HMAC algorithm
#
# @param [String] alg the algorithm to use (one of 'hmac-sha-1', 'hmac-sha-256')
def algorithm=(alg)
@algorithm = case alg.to_s
when 'hmac-sha-1'
OpenSSL::Digest::SHA1.new
when 'hmac-sha-256'
OpenSSL::Digest::SHA256.new
else
fail(ArgumentError, 'Unsupported algorithm')
end
end
private
# No-op since we need the verb and path
# and the MAC always goes in a header
def token=(_)
end
# Base64.strict_encode64 is not available on Ruby 1.8.7
def strict_encode64(str)
Base64.encode64(str).gsub("\n", '')
end
end
end
oauth2-1.0.0/lib/oauth2/response.rb 0000644 0000041 0000041 00000005455 12363614141 017107 0 ustar www-data www-data require 'multi_json'
require 'multi_xml'
require 'rack'
module OAuth2
# OAuth2::Response class
class Response
attr_reader :response
attr_accessor :error, :options
# Adds a new content type parser.
#
# @param [Symbol] key A descriptive symbol key such as :json or :query.
# @param [Array] One or more mime types to which this parser applies.
# @yield [String] A block returning parsed content.
def self.register_parser(key, mime_types, &block)
key = key.to_sym
PARSERS[key] = block
Array(mime_types).each do |mime_type|
CONTENT_TYPES[mime_type] = key
end
end
# Initializes a Response instance
#
# @param [Faraday::Response] response The Faraday response instance
# @param [Hash] opts options in which to initialize the instance
# @option opts [Symbol] :parse (:automatic) how to parse the response body. one of :query (for x-www-form-urlencoded),
# :json, or :automatic (determined by Content-Type response header)
def initialize(response, opts = {})
@response = response
@options = {:parse => :automatic}.merge(opts)
end
# The HTTP response headers
def headers
response.headers
end
# The HTTP response status code
def status
response.status
end
# The HTTP response body
def body
response.body || ''
end
# Procs that, when called, will parse a response body according
# to the specified format.
PARSERS = {
:json => lambda { |body| MultiJson.load(body) rescue body }, # rubocop:disable RescueModifier
:query => lambda { |body| Rack::Utils.parse_query(body) },
:text => lambda { |body| body }
}
# Content type assignments for various potential HTTP content types.
CONTENT_TYPES = {
'application/json' => :json,
'text/javascript' => :json,
'application/x-www-form-urlencoded' => :query,
'text/plain' => :text
}
# The parsed response body.
# Will attempt to parse application/x-www-form-urlencoded and
# application/json Content-Type response bodies
def parsed
return nil unless PARSERS.key?(parser)
@parsed ||= PARSERS[parser].call(body)
end
# Attempts to determine the content type of the response.
def content_type
((response.headers.values_at('content-type', 'Content-Type').compact.first || '').split(';').first || '').strip
end
# Determines the parser that will be used to supply the content of #parsed
def parser
return options[:parse].to_sym if PARSERS.key?(options[:parse])
CONTENT_TYPES[content_type]
end
end
end
OAuth2::Response.register_parser(:xml, ['text/xml', 'application/rss+xml', 'application/rdf+xml', 'application/atom+xml']) do |body|
MultiXml.parse(body) rescue body # rubocop:disable RescueModifier
end
oauth2-1.0.0/lib/oauth2/access_token.rb 0000644 0000041 0000041 00000013540 12363614141 017704 0 ustar www-data www-data module OAuth2
class AccessToken
attr_reader :client, :token, :expires_in, :expires_at, :params
attr_accessor :options, :refresh_token
class << self
# Initializes an AccessToken from a Hash
#
# @param [Client] the OAuth2::Client instance
# @param [Hash] a hash of AccessToken property values
# @return [AccessToken] the initalized AccessToken
def from_hash(client, hash)
new(client, hash.delete('access_token') || hash.delete(:access_token), hash)
end
# Initializes an AccessToken from a key/value application/x-www-form-urlencoded string
#
# @param [Client] client the OAuth2::Client instance
# @param [String] kvform the application/x-www-form-urlencoded string
# @return [AccessToken] the initalized AccessToken
def from_kvform(client, kvform)
from_hash(client, Rack::Utils.parse_query(kvform))
end
end
# Initalize an AccessToken
#
# @param [Client] client the OAuth2::Client instance
# @param [String] token the Access Token value
# @param [Hash] opts the options to create the Access Token with
# @option opts [String] :refresh_token (nil) the refresh_token value
# @option opts [FixNum, String] :expires_in (nil) the number of seconds in which the AccessToken will expire
# @option opts [FixNum, String] :expires_at (nil) the epoch time in seconds in which AccessToken will expire
# @option opts [Symbol] :mode (:header) the transmission mode of the Access Token parameter value
# one of :header, :body or :query
# @option opts [String] :header_format ('Bearer %s') the string format to use for the Authorization header
# @option opts [String] :param_name ('access_token') the parameter name to use for transmission of the
# Access Token value in :body or :query transmission mode
def initialize(client, token, opts = {})
@client = client
@token = token.to_s
[:refresh_token, :expires_in, :expires_at].each do |arg|
instance_variable_set("@#{arg}", opts.delete(arg) || opts.delete(arg.to_s))
end
@expires_in ||= opts.delete('expires')
@expires_in &&= @expires_in.to_i
@expires_at &&= @expires_at.to_i
@expires_at ||= Time.now.to_i + @expires_in if @expires_in
@options = {:mode => opts.delete(:mode) || :header,
:header_format => opts.delete(:header_format) || 'Bearer %s',
:param_name => opts.delete(:param_name) || 'access_token'}
@params = opts
end
# Indexer to additional params present in token response
#
# @param [String] key entry key to Hash
def [](key)
@params[key]
end
# Whether or not the token expires
#
# @return [Boolean]
def expires?
!!@expires_at # rubocop:disable DoubleNegation
end
# Whether or not the token is expired
#
# @return [Boolean]
def expired?
expires? && (expires_at < Time.now.to_i)
end
# Refreshes the current Access Token
#
# @return [AccessToken] a new AccessToken
# @note options should be carried over to the new AccessToken
def refresh!(params = {})
fail('A refresh_token is not available') unless refresh_token
params.merge!(:client_id => @client.id,
:client_secret => @client.secret,
:grant_type => 'refresh_token',
:refresh_token => refresh_token)
new_token = @client.get_token(params)
new_token.options = options
new_token.refresh_token = refresh_token unless new_token.refresh_token
new_token
end
# Convert AccessToken to a hash which can be used to rebuild itself with AccessToken.from_hash
#
# @return [Hash] a hash of AccessToken property values
def to_hash
params.merge(:access_token => token, :refresh_token => refresh_token, :expires_at => expires_at)
end
# Make a request with the Access Token
#
# @param [Symbol] verb the HTTP request method
# @param [String] path the HTTP URL path of the request
# @param [Hash] opts the options to make the request with
# @see Client#request
def request(verb, path, opts = {}, &block)
self.token = opts
@client.request(verb, path, opts, &block)
end
# Make a GET request with the Access Token
#
# @see AccessToken#request
def get(path, opts = {}, &block)
request(:get, path, opts, &block)
end
# Make a POST request with the Access Token
#
# @see AccessToken#request
def post(path, opts = {}, &block)
request(:post, path, opts, &block)
end
# Make a PUT request with the Access Token
#
# @see AccessToken#request
def put(path, opts = {}, &block)
request(:put, path, opts, &block)
end
# Make a PATCH request with the Access Token
#
# @see AccessToken#request
def patch(path, opts = {}, &block)
request(:patch, path, opts, &block)
end
# Make a DELETE request with the Access Token
#
# @see AccessToken#request
def delete(path, opts = {}, &block)
request(:delete, path, opts, &block)
end
# Get the headers hash (includes Authorization token)
def headers
{'Authorization' => options[:header_format] % token}
end
private
def token=(opts) # rubocop:disable MethodLength
case options[:mode]
when :header
opts[:headers] ||= {}
opts[:headers].merge!(headers)
when :query
opts[:params] ||= {}
opts[:params][options[:param_name]] = token
when :body
opts[:body] ||= {}
if opts[:body].is_a?(Hash)
opts[:body][options[:param_name]] = token
else
opts[:body] << "{options[:param_name]}=#{token}"
end
# @todo support for multi-part (file uploads)
else
fail("invalid :mode option of #{options[:mode]}")
end
end
end
end
oauth2-1.0.0/lib/oauth2/strategy/ 0000755 0000041 0000041 00000000000 12363614141 016555 5 ustar www-data www-data oauth2-1.0.0/lib/oauth2/strategy/client_credentials.rb 0000644 0000041 0000041 00000002422 12363614141 022735 0 ustar www-data www-data require 'base64'
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
fail(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' => authorization(client_params['client_id'], client_params['client_secret'])}})
@client.get_token(params, opts.merge('refresh_token' => nil))
end
# Returns the Authorization header value for Basic Authentication
#
# @param [String] The client ID
# @param [String] the client secret
def authorization(client_id, client_secret)
'Basic ' + Base64.encode64(client_id + ':' + client_secret).gsub("\n", '')
end
end
end
end
oauth2-1.0.0/lib/oauth2/strategy/password.rb 0000644 0000041 0000041 00000001705 12363614141 020747 0 ustar www-data www-data module OAuth2
module Strategy
# The Resource Owner Password Credentials Authorization Strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
class Password < Base
# Not used for this strategy
#
# @raise [NotImplementedError]
def authorize_url
fail(NotImplementedError, 'The authorization endpoint is not used in this strategy')
end
# Retrieve an access token given the specified End User username and password.
#
# @param [String] username the End User username
# @param [String] password the End User password
# @param [Hash] params additional params
def get_token(username, password, params = {}, opts = {})
params = {'grant_type' => 'password',
'username' => username,
'password' => password}.merge(client_params).merge(params)
@client.get_token(params, opts)
end
end
end
end
oauth2-1.0.0/lib/oauth2/strategy/assertion.rb 0000644 0000041 0000041 00000004655 12363614141 021123 0 ustar www-data www-data require 'jwt'
module OAuth2
module Strategy
# The Client Assertion Strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
#
# Sample usage:
# client = OAuth2::Client.new(client_id, client_secret,
# :site => 'http://localhost:8080')
#
# params = {:hmac_secret => "some secret",
# # or :private_key => "private key string",
# :iss => "http://localhost:3001",
# :prn => "me@here.com",
# :exp => Time.now.utc.to_i + 3600}
#
# access = client.assertion.get_token(params)
# access.token # actual access_token string
# access.get("/api/stuff") # making api calls with access token in header
#
class Assertion < Base
# Not used for this strategy
#
# @raise [NotImplementedError]
def authorize_url
fail(NotImplementedError, 'The authorization endpoint is not used in this strategy')
end
# Retrieve an access token given the specified client.
#
# @param [Hash] params assertion params
# pass either :hmac_secret or :private_key, but not both.
#
# params :hmac_secret, secret string.
# params :private_key, private key string.
#
# params :iss, issuer
# params :aud, audience, optional
# params :prn, principal, current user
# params :exp, expired at, in seconds, like Time.now.utc.to_i + 3600
#
# @param [Hash] opts options
def get_token(params = {}, opts = {})
hash = build_request(params)
@client.get_token(hash, opts.merge('refresh_token' => nil))
end
def build_request(params)
assertion = build_assertion(params)
{:grant_type => 'assertion',
:assertion_type => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
:assertion => assertion,
:scope => params[:scope]
}.merge(client_params)
end
def build_assertion(params)
claims = {:iss => params[:iss],
:aud => params[:aud],
:prn => params[:prn],
:exp => params[:exp]
}
if params[:hmac_secret]
JWT.encode(claims, params[:hmac_secret], 'HS256')
elsif params[:private_key]
JWT.encode(claims, params[:private_key], 'RS256')
end
end
end
end
end
oauth2-1.0.0/lib/oauth2/strategy/implicit.rb 0000644 0000041 0000041 00000001556 12363614141 020723 0 ustar www-data www-data module OAuth2
module Strategy
# The Implicit Strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
class Implicit < Base
# The required query parameters for the authorize URL
#
# @param [Hash] params additional query parameters
def authorize_params(params = {})
params.merge('response_type' => 'token', 'client_id' => @client.id)
end
# The authorization URL endpoint of the provider
#
# @param [Hash] params additional query parameters for the URL
def authorize_url(params = {})
@client.authorize_url(authorize_params.merge(params))
end
# Not used for this strategy
#
# @raise [NotImplementedError]
def get_token(*)
fail(NotImplementedError, 'The token is accessed differently in this strategy')
end
end
end
end
oauth2-1.0.0/lib/oauth2/strategy/base.rb 0000644 0000041 0000041 00000000470 12363614141 020015 0 ustar www-data www-data module OAuth2
module Strategy
class Base
def initialize(client)
@client = client
end
# The OAuth client_id and client_secret
#
# @return [Hash]
def client_params
{'client_id' => @client.id, 'client_secret' => @client.secret}
end
end
end
end
oauth2-1.0.0/lib/oauth2/strategy/auth_code.rb 0000644 0000041 0000041 00000002256 12363614141 021042 0 ustar www-data www-data module OAuth2
module Strategy
# The Authorization Code Strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
class AuthCode < Base
# The required query parameters for the authorize URL
#
# @param [Hash] params additional query parameters
def authorize_params(params = {})
params.merge('response_type' => 'code', 'client_id' => @client.id)
end
# The authorization URL endpoint of the provider
#
# @param [Hash] params additional query parameters for the URL
def authorize_url(params = {})
@client.authorize_url(authorize_params.merge(params))
end
# Retrieve an access token given the specified validation code.
#
# @param [String] code The Authorization Code value
# @param [Hash] params additional params
# @param [Hash] opts options
# @note that you must also provide a :redirect_uri with most OAuth 2.0 providers
def get_token(code, params = {}, opts = {})
params = {'grant_type' => 'authorization_code', 'code' => code}.merge(client_params).merge(params)
@client.get_token(params, opts)
end
end
end
end
oauth2-1.0.0/lib/oauth2/client.rb 0000644 0000041 0000041 00000015201 12363614141 016515 0 ustar www-data www-data require 'faraday'
require 'logger'
module OAuth2
# The OAuth2::Client class
class Client
attr_reader :id, :secret, :site
attr_accessor :options
attr_writer :connection
# Instantiate a new OAuth 2.0 client using the
# Client ID and Client Secret registered to your
# application.
#
# @param [String] client_id the client_id value
# @param [String] client_secret the client_secret value
# @param [Hash] opts the options to create the client with
# @option opts [String] :site the OAuth2 provider site host
# @option opts [String] :authorize_url ('/oauth/authorize') absolute or relative URL path to the Authorization endpoint
# @option opts [String] :token_url ('/oauth/token') absolute or relative URL path to the Token endpoint
# @option opts [Symbol] :token_method (:post) HTTP method to use to request token (:get or :post)
# @option opts [Hash] :connection_opts ({}) Hash of connection options to pass to initialize Faraday with
# @option opts [FixNum] :max_redirects (5) maximum number of redirects to follow
# @option opts [Boolean] :raise_errors (true) whether or not to raise an OAuth2::Error
# on responses with 400+ status codes
# @yield [builder] The Faraday connection builder
def initialize(client_id, client_secret, options = {}, &block)
opts = options.dup
@id = client_id
@secret = client_secret
@site = opts.delete(:site)
ssl = opts.delete(:ssl)
@options = {:authorize_url => '/oauth/authorize',
:token_url => '/oauth/token',
:token_method => :post,
:connection_opts => {},
:connection_build => block,
:max_redirects => 5,
:raise_errors => true}.merge(opts)
@options[:connection_opts][:ssl] = ssl if ssl
end
# Set the site host
#
# @param [String] the OAuth2 provider site host
def site=(value)
@connection = nil
@site = value
end
# The Faraday connection object
def connection
@connection ||= begin
conn = Faraday.new(site, options[:connection_opts])
conn.build do |b|
options[:connection_build].call(b)
end if options[:connection_build]
conn
end
end
# The authorize endpoint URL of the OAuth2 provider
#
# @param [Hash] params additional query parameters
def authorize_url(params = nil)
connection.build_url(options[:authorize_url], params).to_s
end
# The token endpoint URL of the OAuth2 provider
#
# @param [Hash] params additional query parameters
def token_url(params = nil)
connection.build_url(options[:token_url], params).to_s
end
# Makes a request relative to the specified site root.
#
# @param [Symbol] verb one of :get, :post, :put, :delete
# @param [String] url URL path of request
# @param [Hash] opts the options to make the request with
# @option opts [Hash] :params additional query parameters for the URL of the request
# @option opts [Hash, String] :body the body of the request
# @option opts [Hash] :headers http request headers
# @option opts [Boolean] :raise_errors whether or not to raise an OAuth2::Error on 400+ status
# code response for this request. Will default to client option
# @option opts [Symbol] :parse @see Response::initialize
# @yield [req] The Faraday request
def request(verb, url, opts = {}) # rubocop:disable CyclomaticComplexity, MethodLength
connection.response :logger, ::Logger.new($stdout) if ENV['OAUTH_DEBUG'] == 'true'
url = connection.build_url(url, opts[:params]).to_s
response = connection.run_request(verb, url, opts[:body], opts[:headers]) do |req|
yield(req) if block_given?
end
response = Response.new(response, :parse => opts[:parse])
case response.status
when 301, 302, 303, 307
opts[:redirect_count] ||= 0
opts[:redirect_count] += 1
return response if opts[:redirect_count] > options[:max_redirects]
if response.status == 303
verb = :get
opts.delete(:body)
end
request(verb, response.headers['location'], opts)
when 200..299, 300..399
# on non-redirecting 3xx statuses, just return the response
response
when 400..599
error = Error.new(response)
fail(error) if opts.fetch(:raise_errors, options[:raise_errors])
response.error = error
response
else
error = Error.new(response)
fail(error, "Unhandled status code value of #{response.status}")
end
end
# Initializes an AccessToken by making a request to the token endpoint
#
# @param [Hash] params a Hash of params for the token endpoint
# @param [Hash] access token options, to pass to the AccessToken object
# @param [Class] class of access token for easier subclassing OAuth2::AccessToken
# @return [AccessToken] the initalized AccessToken
def get_token(params, access_token_opts = {}, access_token_class = AccessToken)
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
response = request(options[:token_method], token_url, opts)
error = Error.new(response)
fail(error) if options[:raise_errors] && !(response.parsed.is_a?(Hash) && response.parsed['access_token'])
access_token_class.from_hash(self, response.parsed.merge(access_token_opts))
end
# The Authorization Code strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.1
def auth_code
@auth_code ||= OAuth2::Strategy::AuthCode.new(self)
end
# The Implicit strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-4.2
def implicit
@implicit ||= OAuth2::Strategy::Implicit.new(self)
end
# The Resource Owner Password Credentials strategy
#
# @see http://tools.ietf.org/html/draft-ietf-oauth-v2-15#section-4.3
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
def assertion
@assertion ||= OAuth2::Strategy::Assertion.new(self)
end
end
end
oauth2-1.0.0/lib/oauth2/version.rb 0000644 0000041 0000041 00000000342 12363614141 016724 0 ustar www-data www-data module OAuth2
class Version
MAJOR = 1
MINOR = 0
PATCH = 0
PRE = nil
class << self
# @return [String]
def to_s
[MAJOR, MINOR, PATCH, PRE].compact.join('.')
end
end
end
end
oauth2-1.0.0/lib/oauth2/error.rb 0000644 0000041 0000041 00000001161 12363614141 016370 0 ustar www-data www-data module OAuth2
class Error < StandardError
attr_reader :response, :code, :description
# standard error values include:
# :invalid_request, :invalid_client, :invalid_token, :invalid_grant, :unsupported_grant_type, :invalid_scope
def initialize(response)
response.error = self
@response = response
message = []
if response.parsed.is_a?(Hash)
@code = response.parsed['error']
@description = response.parsed['error_description']
message << "#{@code}: #{@description}"
end
message << response.body
super(message.join("\n"))
end
end
end
oauth2-1.0.0/lib/oauth2.rb 0000644 0000041 0000041 00000000534 12363614141 015242 0 ustar www-data www-data require 'oauth2/error'
require 'oauth2/client'
require 'oauth2/strategy/base'
require 'oauth2/strategy/auth_code'
require 'oauth2/strategy/implicit'
require 'oauth2/strategy/password'
require 'oauth2/strategy/client_credentials'
require 'oauth2/strategy/assertion'
require 'oauth2/access_token'
require 'oauth2/mac_token'
require 'oauth2/response'
oauth2-1.0.0/metadata.yml 0000644 0000041 0000041 00000010763 12363614141 015255 0 ustar www-data www-data --- !ruby/object:Gem::Specification
name: oauth2
version: !ruby/object:Gem::Version
version: 1.0.0
platform: ruby
authors:
- Michael Bleigh
- Erik Michaels-Ober
autorequire:
bindir: bin
cert_chain: []
date: 2014-07-09 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: faraday
requirement: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0.8'
- - "<"
- !ruby/object:Gem::Version
version: '0.10'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0.8'
- - "<"
- !ruby/object:Gem::Version
version: '0.10'
- !ruby/object:Gem::Dependency
name: jwt
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.0'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.0'
- !ruby/object:Gem::Dependency
name: multi_json
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.3'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.3'
- !ruby/object:Gem::Dependency
name: multi_xml
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '0.5'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '0.5'
- !ruby/object:Gem::Dependency
name: rack
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.2'
type: :runtime
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.2'
- !ruby/object:Gem::Dependency
name: bundler
requirement: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
requirements:
- - "~>"
- !ruby/object:Gem::Version
version: '1.0'
description: A Ruby wrapper for the OAuth 2.0 protocol built with a similar style
to the original OAuth spec.
email:
- michael@intridea.com
- sferik@gmail.com
executables: []
extensions: []
extra_rdoc_files: []
files:
- ".document"
- CONTRIBUTING.md
- LICENSE.md
- README.md
- Rakefile
- lib/oauth2.rb
- lib/oauth2/access_token.rb
- lib/oauth2/client.rb
- lib/oauth2/error.rb
- lib/oauth2/mac_token.rb
- lib/oauth2/response.rb
- lib/oauth2/strategy/assertion.rb
- lib/oauth2/strategy/auth_code.rb
- lib/oauth2/strategy/base.rb
- lib/oauth2/strategy/client_credentials.rb
- lib/oauth2/strategy/implicit.rb
- lib/oauth2/strategy/password.rb
- lib/oauth2/version.rb
- oauth2.gemspec
- spec/helper.rb
- spec/oauth2/access_token_spec.rb
- spec/oauth2/client_spec.rb
- spec/oauth2/mac_token_spec.rb
- spec/oauth2/response_spec.rb
- spec/oauth2/strategy/assertion_spec.rb
- spec/oauth2/strategy/auth_code_spec.rb
- spec/oauth2/strategy/base_spec.rb
- spec/oauth2/strategy/client_credentials_spec.rb
- spec/oauth2/strategy/implicit_spec.rb
- spec/oauth2/strategy/password_spec.rb
homepage: http://github.com/intridea/oauth2
licenses:
- MIT
metadata: {}
post_install_message:
rdoc_options: []
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: '0'
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: 1.3.5
requirements: []
rubyforge_project:
rubygems_version: 2.2.2
signing_key:
specification_version: 4
summary: A Ruby wrapper for the OAuth 2.0 protocol.
test_files:
- spec/helper.rb
- spec/oauth2/access_token_spec.rb
- spec/oauth2/client_spec.rb
- spec/oauth2/mac_token_spec.rb
- spec/oauth2/response_spec.rb
- spec/oauth2/strategy/assertion_spec.rb
- spec/oauth2/strategy/auth_code_spec.rb
- spec/oauth2/strategy/base_spec.rb
- spec/oauth2/strategy/client_credentials_spec.rb
- spec/oauth2/strategy/implicit_spec.rb
- spec/oauth2/strategy/password_spec.rb
has_rdoc:
oauth2-1.0.0/CONTRIBUTING.md 0000644 0000041 0000041 00000001454 12363614141 015200 0 ustar www-data www-data ## Submitting a Pull Request
1. [Fork the repository.][fork]
2. [Create a topic branch.][branch]
3. Add specs for your unimplemented feature or bug fix.
4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
5. Implement your feature or bug fix.
6. Run `bundle exec rake`. If your specs fail, return to step 5.
7. Run `open coverage/index.html`. If your changes are not completely covered
by your tests, return to step 3.
8. Add documentation for your feature or bug fix.
9. Run `bundle exec rake verify_measurements`. If your changes are not 100%
documented, go back to step 8.
10. Commit and push your changes.
11. [Submit a pull request.][pr]
[fork]: http://help.github.com/fork-a-repo/
[branch]: http://learn.github.com/p/branching.html
[pr]: http://help.github.com/send-pull-requests/
oauth2-1.0.0/checksums.yaml.gz 0000444 0000041 0000041 00000000416 12363614141 016232 0 ustar www-data www-data ʽSe;@DYl`<|^ff
4F&\5Vz=yG;ܡǵ(T4#z0N}2ɑ4;̽{SRmݸcl3]ӼSx+B[n$T(o`_KVYdKU:NC
Af%4!wOؠ.obҋ7G`T
#{IKFj[5_%x1ʒ#]SJ+ BgE oauth2-1.0.0/README.md 0000644 0000041 0000041 00000012636 12363614141 014232 0 ustar www-data www-data # OAuth2
[][gem]
[][travis]
[][gemnasium]
[][codeclimate]
[][coveralls]
[gem]: https://rubygems.org/gems/oauth2
[travis]: http://travis-ci.org/intridea/oauth2
[gemnasium]: https://gemnasium.com/intridea/oauth2
[codeclimate]: https://codeclimate.com/github/intridea/oauth2
[coveralls]: https://coveralls.io/r/intridea/oauth2
A Ruby wrapper for the OAuth 2.0 specification.
## Installation
gem install oauth2
## Resources
* [View Source on GitHub][code]
* [Report Issues on GitHub][issues]
* [Read More at the Wiki][wiki]
[code]: https://github.com/intridea/oauth2
[issues]: https://github.com/intridea/oauth2/issues
[wiki]: https://wiki.github.com/intridea/oauth2
## Usage Examples
```ruby
require 'oauth2'
client = OAuth2::Client.new('client_id', 'client_secret', :site => 'https://example.org')
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', :headers => {'Authorization' => 'Basic some_password'})
response = token.get('/api/resource', :params => { 'query_foo' => 'bar' })
response.class.name
# => OAuth2::Response
```
## OAuth2::Response
The AccessToken methods #get, #post, #put and #delete and the generic #request
will return an instance of the #OAuth2::Response class.
This instance contains a #parsed method that will parse the response body and
return a Hash if the Content-Type is application/x-www-form-urlencoded or if
the body is a JSON object. It will return an Array if the body is a JSON
array. Otherwise, it will return the original body string.
The original response body, headers, and status can be accessed via their
respective methods.
## OAuth2::AccessToken
If you have an existing Access Token for a user, you can initialize an instance
using various class methods including the standard new, from_hash (if you have
a hash of the values), or from_kvform (if you have an
application/x-www-form-urlencoded encoded string of the values).
## OAuth2::Error
On 400+ status code responses, an OAuth2::Error will be raised. If it is a
standard OAuth2 error response, the body will be parsed and #code and #description will contain the values provided from the error and
error_description parameters. The #response property of OAuth2::Error will
always contain the OAuth2::Response instance.
If you do not want an error to be raised, you may use :raise_errors => false
option on initialization of the client. In this case the OAuth2::Response
instance will be returned as usual and on 400+ status code responses, the
Response instance will contain the OAuth2::Error instance.
## Authorization Grants
Currently the Authorization Code, Implicit, Resource Owner Password Credentials, Client Credentials, and Assertion
authentication grant types have helper strategy classes that simplify client
use. They are available via the #auth_code, #implicit, #password, #client_credentials, and #assertion methods respectively.
```ruby
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')
auth_url = client.implicit.authorize_url(:redirect_uri => 'http://localhost:8080/oauth/callback')
# get the token params in the callback and
token = OAuth2::AccessToken.from_kvform(client, query_string)
token = client.password.get_token('username', 'password')
token = client.client_credentials.get_token
token = client.assertion.get_token(assertion_params)
```
If you want to specify additional headers to be sent out with the
request, add a 'headers' hash under 'params':
```ruby
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.
## Supported Ruby Versions
This library aims to support and is [tested against][travis] the following Ruby
implementations:
* Ruby 1.8.7
* Ruby 1.9.2
* Ruby 1.9.3
* Ruby 2.0.0
* Ruby 2.1.0
* [JRuby][]
* [Rubinius][]
[jruby]: http://jruby.org/
[rubinius]: http://rubini.us/
If something doesn't work on one of these interpreters, it's a bug.
This library may inadvertently work (or seem to work) on other Ruby
implementations, however support will only be provided for the versions listed
above.
If you would like this library to support another Ruby version, you may
volunteer to be a maintainer. Being a maintainer entails making sure all tests
run and pass on that implementation. When something breaks on your
implementation, you will be responsible for providing patches in a timely
fashion. If critical issues for a particular implementation exist at the time
of a major release, support for that Ruby version may be dropped.
## License
Copyright (c) 2011-2013 Michael Bleigh and Intridea, Inc. See [LICENSE][] for
details.
[license]: LICENSE.md
oauth2-1.0.0/.document 0000644 0000041 0000041 00000000074 12363614141 014563 0 ustar www-data www-data README.rdoc
lib/**/*.rb
bin/*
features/**/*.feature
LICENSE