Moved 37signals into a single OAuth2 class.

This commit is contained in:
Michael Bleigh 2010-06-23 20:15:43 -04:00
parent 00de993ae4
commit db45e50b7b
9 changed files with 96 additions and 302 deletions

View File

@ -10,7 +10,5 @@ module OmniAuth
autoload :Facebook, 'omniauth/strategies/facebook'
autoload :GitHub, 'omniauth/strategies/github'
autoload :ThirtySevenSignals, 'omniauth/strategies/thirty_seven_signals'
autoload :Basecamp, 'omniauth/strategies/basecamp'
autoload :Campfire, 'omniauth/strategies/campfire'
end
end

View File

@ -1,61 +0,0 @@
require 'omniauth/oauth'
require 'nokogiri'
module OmniAuth
module Strategies
#
# Authenticate to Basecamp utilizing OAuth 2.0 and retrieve
# basic user information.
#
# Usage:
#
# use OmniAuth::Strategies::Basecamp, 'app_id', 'app_secret'
class Basecamp < ThirtySevenSignals
def initialize(app, client_id, client_secret, options = {})
super(app, :basecamp, client_id, client_secret, options)
end
protected
def user_data
@data ||= Nokogiri::XML.parse(@access_token.get('/users/me.xml'))
end
def site_url
"https://#{subdomain}.basecamphq.com"
end
def auth_hash
doc = user_data
OmniAuth::Utils.deep_merge(super, {
'uid' => doc.xpath('person/id').text,
'user_info' => user_info(doc),
'credentials' => {
'token' => doc.xpath('person/token').text
},
'extra' => {
'access_token' => @access_token
}
})
end
def user_info(doc)
hash = {
'first_name' => doc.xpath('person/first-name').text,
'last_name' => doc.xpath('person/last-name').text,
'email' => doc.xpath('person/email-address').text,
'image' => doc.xpath('person/avatar-url').text
}
hash['name'] = [hash['first_name'], hash['last_name']].join(' ').strip
hash.delete('image') if hash['image'].include?('missing/avatar.png')
hash
end
end
end
end

View File

@ -1,52 +0,0 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
#
# Authenticate to Campfire utilizing OAuth 2.0 and retrieve
# basic user information.
#
# Usage:
#
# use OmniAuth::Strategies::Campfire, 'app_id', 'app_secret'
class Campfire < ThirtySevenSignals
def initialize(app, app_id, app_secret, options = {})
super(app, :campfire, app_id, app_secret, options)
end
protected
def user_data
@data ||= MultiJson.decode(@access_token.get('/users/me.json'))
end
def site_url
"https://#{subdomain}.campfirenow.com"
end
def auth_hash
data = self.user_data
OmniAuth::Utils.deep_merge(super, {
'uid' => data['user']['id'].to_s,
'user_info' => user_info(data),
'credentials' => {
'token' => data['api_auth_token']
},
'extra' => {
'access_token' => @access_token
}
})
end
def user_info(hash)
{
'name' => hash['name'],
'email' => hash['email_address']
}
end
end
end
end

View File

@ -1,56 +1,38 @@
require 'omniauth/oauth'
require 'multi_json'
module OmniAuth
module Strategies
# Abstract Strategy for 37Signals OAuth2 providers.
class ThirtySevenSignals < OAuth2
SUBDOMAIN_PARAMETER = 'subdomain'
def initialize(app, name, client_id, client_secret, options = {})
super(app, name, client_id, client_secret, options)
def initialize(app, app_id, app_secret, options = {})
options[:site] = 'https://launchpad.37signals.com/'
options[:authorize_path] = '/authorization/new'
options[:access_token_path] = '/authorization/token'
super(app, :thirty_seven_signals, app_id, app_secret, options)
end
protected
def client
::OAuth2::Client.new(@client.id, @client.secret, :site => site_url)
def user_data
@data ||= MultiJson.decode(@access_token.get('/authorization.json'))
end
def request_phase
if subdomain
super
else
ask_for_subdomain
end
def user_info
{
'email' => user_data['identity']['email_address'],
'first_name' => user_data['identity']['first_name'],
'last_name' => user_data['identity']['last_name'],
'name' => [user_data['identity']['first_name'], user_data['identity']['last_name']].join(' ').strip
}
end
def callback_phase
if subdomain
super
else
ask_for_subdomain
end
def auth_hash
OmniAuth::Utils.deep_merge(super, {
'uid' => user_data['identity']['id'],
'user_info' => user_info,
'extra' => {
'accounts' => user_data['accounts']
}
})
end
def ask_for_subdomain
n = self.name.to_s.capitalize
OmniAuth::Form.build("#{n} Subdomain Required") do
text_field "#{n} Subdomain", ::OmniAuth::Strategies::ThirtySevenSignals::SUBDOMAIN_PARAMETER
end.to_response
end
def subdomain
((request.session[:oauth] ||= {})[name.to_sym] ||= {})[:subdomain] ||= request.params[SUBDOMAIN_PARAMETER]
end
def site_url
raise NotImplementedError.new("Subclasses must define #{site_url}")
end
end
end
end

View File

@ -14,11 +14,10 @@ Gem::Specification.new do |gem|
gem.files = Dir.glob("{lib}/**/*") + %w(README.rdoc LICENSE.rdoc CHANGELOG.rdoc)
gem.add_dependency 'oa-core', version
gem.add_dependency 'rack', '~> 1.1.0'
gem.add_dependency 'multi_json', '~> 0.0.2'
gem.add_dependency 'nokogiri', '~> 1.4.2'
gem.add_dependency 'oauth', '~> 0.4.0'
gem.add_dependency 'oauth2', '~> 0.0.8'
gem.add_dependency 'oauth2', '~> 0.0.10'
eval File.read(File.join(File.dirname(__FILE__), '../development_dependencies.rb'))
end

View File

@ -1,72 +0,0 @@
require File.dirname(__FILE__) + '/../../spec_helper'
describe OmniAuth::Strategies::Basecamp, :type => :strategy do
include OmniAuth::Test::StrategyTestCase
def strategy
[OmniAuth::Strategies::Basecamp, 'abc', 'def']
end
describe '/auth/basecamp without a subdomain' do
before do
get '/auth/basecamp'
end
it 'should respond with OK' do
last_response.should be_ok
end
it 'should respond with HTML' do
last_response.content_type.should == 'text/html'
end
it 'should render a subdomain input' do
last_response.body.should =~ %r{<input[^>]*subdomain}
end
end
describe 'POST /auth/basecamp with a subdomain' do
before do
# the middleware doesn't actually care that it's a POST,
# but it makes the "redirect_to" calculation down below easier
# since the params are passed in the body rather than the URL.
post '/auth/basecamp', {OmniAuth::Strategies::ThirtySevenSignals::SUBDOMAIN_PARAMETER => 'flugle'}
end
it 'should redirect to the proper authorize_url' do
last_response.should be_redirect
redirect_to = CGI.escape(last_request.url + '/callback')
last_response.headers['Location'].should == "https://flugle.basecamphq.com/oauth/authorize?client_id=abc&redirect_uri=#{redirect_to}&type=web_server"
end
it 'should set the basecamp subdomain in the session' do
session[:oauth][:basecamp][:subdomain].should == 'flugle'
end
end
describe 'followed by GET /auth/basecamp/callback' do
before do
stub_request(:post, 'https://flugle.basecamphq.com/oauth/access_token').
to_return(:body => %q{{"access_token": "your_token"}})
stub_request(:get, 'https://flugle.basecamphq.com/users/me.xml?access_token=your_token').
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'basecamp_200.xml')))
get '/auth/basecamp/callback?code=plums', {}, {'rack.session' => {:oauth => {:basecamp => {:subdomain => 'flugle'}}}}
end
sets_an_auth_hash
sets_provider_to 'basecamp'
sets_uid_to '1827370'
it 'should exchange the request token for an access token' do
token = last_request['auth']['extra']['access_token']
token.should be_kind_of(OAuth2::AccessToken)
token.token.should == 'your_token'
end
it 'should call through to the master app' do
last_response.body.should == 'true'
end
end
end

View File

@ -1,72 +0,0 @@
require File.dirname(__FILE__) + '/../../spec_helper'
describe OmniAuth::Strategies::Campfire, :type => :strategy do
include OmniAuth::Test::StrategyTestCase
def strategy
[OmniAuth::Strategies::Campfire, 'abc', 'def']
end
describe '/auth/campfire without a subdomain' do
before do
get '/auth/campfire'
end
it 'should respond with OK' do
last_response.should be_ok
end
it 'should respond with HTML' do
last_response.content_type.should == 'text/html'
end
it 'should render a subdomain input' do
last_response.body.should =~ %r{<input[^>]*subdomain}
end
end
describe 'POST /auth/campfire with a subdomain' do
before do
# the middleware doesn't actually care that it's a POST,
# but it makes the "redirect_to" calculation down below easier
# since the params are passed in the body rather than the URL.
post '/auth/campfire', {OmniAuth::Strategies::ThirtySevenSignals::SUBDOMAIN_PARAMETER => 'flugle'}
end
it 'should redirect to the proper authorize_url' do
last_response.should be_redirect
redirect_to = CGI.escape(last_request.url + '/callback')
last_response.headers['Location'].should == "https://flugle.campfirenow.com/oauth/authorize?client_id=abc&redirect_uri=#{redirect_to}&type=web_server"
end
it 'should set the campfire subdomain in the session' do
session[:oauth][:campfire][:subdomain].should == 'flugle'
end
end
describe 'followed by GET /auth/campfire/callback' do
before do
stub_request(:post, 'https://flugle.campfirenow.com/oauth/access_token').
to_return(:body => %q{{"access_token": "your_token"}})
stub_request(:get, 'https://flugle.campfirenow.com/users/me.json?access_token=your_token').
to_return(:body => File.read(File.join(File.dirname(__FILE__), '..', '..', 'fixtures', 'campfire_200.json')))
get '/auth/campfire/callback?code=plums', {}, {'rack.session' => {:oauth => {:campfire => {:subdomain => 'flugle'}}}}
end
sets_an_auth_hash
sets_provider_to 'campfire'
sets_uid_to '92718'
it 'should exchange the request token for an access token' do
token = last_request['auth']['extra']['access_token']
token.should be_kind_of(OAuth2::AccessToken)
token.token.should == 'your_token'
end
it 'should call through to the master app' do
last_response.body.should == 'true'
end
end
end

View File

@ -0,0 +1,13 @@
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
describe OmniAuth::Strategies::ThirtySevenSignals do
it 'should subclass OAuth2' do
OmniAuth::Strategies::ThirtySevenSignals.should < OmniAuth::Strategies::OAuth2
end
it 'should initialize with just consumer key and secret' do
lambda{OmniAuth::Strategies::ThirtySevenSignals.new({},'abc','def')}.should_not raise_error
end
end

59
rspec.watchr Normal file
View File

@ -0,0 +1,59 @@
# Run me with:
#
# $ watchr specs.watchr
# --------------------------------------------------
# Convenience Methods
# --------------------------------------------------
def all_spec_files
Dir['*/spec/**/*_spec.rb']
end
def run_spec_matching(thing_to_match)
matches = all_spec_files.grep(/#{thing_to_match}/i)
if matches.empty?
puts "Sorry, thanks for playing, but there were no matches for #{thing_to_match}"
else
run matches.join(' ')
end
end
def run(files_to_run)
puts("Running: #{files_to_run}")
system("clear;rspec -cfs #{files_to_run}")
no_int_for_you
end
def run_all_specs
run(all_spec_files.join(' '))
end
# --------------------------------------------------
# Watchr Rules
# --------------------------------------------------
watch('^[^/]+/spec/(.*)_spec\.rb') { |m| run_spec_matching(m[2]) }
watch('^[^/]+/lib/(.*)\.rb') { |m| run_spec_matching(m[2]) }
watch('^spec/spec_helper\.rb') { run_all_specs }
watch('^(.*)/spec/support/.*\.rb') { run_all_specs }
# --------------------------------------------------
# Signal Handling
# --------------------------------------------------
def no_int_for_you
@sent_an_int = nil
end
Signal.trap 'INT' do
if @sent_an_int then
puts " A second INT? Ok, I get the message. Shutting down now."
exit
else
puts " Did you just send me an INT? Ugh. I'll quit for real if you do it again."
@sent_an_int = true
Kernel.sleep 1.5
run_all_specs
end
end
# vim:ft=ruby