spec/omniauth/strategies/tent_spec.rb in omniauth-tent-0.1.8 vs spec/omniauth/strategies/tent_spec.rb in omniauth-tent-0.2.1
- old
+ new
@@ -1,177 +1,385 @@
require 'spec_helper'
require 'yajl'
+require 'uri'
describe OmniAuth::Strategies::Tent do
- attr_accessor :app
- # customize rack app for testing, if block is given, reverts to default
- # rack app after testing is done
- def set_app!(tent_options = {})
- old_app = self.app
- self.app = Rack::Builder.app do
- use OmniAuth::Strategies::Tent, tent_options
+ def app
+ @app || set_app
+ end
+
+ def set_app(strategy_options = {})
+ @app = Rack::Builder.app do
+ use OmniAuth::Strategies::Tent, strategy_options
run lambda{|env| [404, {'env' => env}, ["HELLO!"]]}
end
- if block_given?
- yield
- self.app = old_app
- end
- self.app
end
- before(:all) do
- set_app!
- end
+ let(:entity_uri) { "http://entity.example.org/xfapc" }
+ let(:server_url) { "http://tent.example.org/xfapc" }
+ let(:server_meta_post_url) { "#{server_url}/posts/#{URI.encode_www_form_component(entity_uri)}/meta-post-id" }
+ let(:link_header) {
+ %(<#{server_meta_post_url}>; rel="https://tent.io/rels/meta-post")
+ }
+ let(:meta_post) {
+ {
+ "id" => "DBhFT_BvKe4zKAPvZJaHMg",
+ "type" => "https://tent.io/types/meta/v0#",
+ "entity" => entity_uri,
+ "published_at" => 1371489957115,
+ "content" => {
+ "entity" => entity_uri,
+ "servers" => [{
+ "version" => "0.3",
+ "urls" => {
+ "oauth_auth" => "#{server_url}/oauth/authorize",
+ "oauth_token" => "#{server_url}/oauth/token",
+ "posts_feed" => "#{server_url}/posts",
+ "new_post" => "#{server_url}/posts",
+ "post" => "#{server_url}/posts/{entity}/{post}",
+ "post_attachment" => "#{server_url}/posts/{entity}/{post}/attachments/{name}",
+ "attachment" => "#{server_url}/attachments/{entity}/{digest}",
+ "batch" => "#{server_url}/batch",
+ "server_info" => "#{server_url}/server"
+ },
+ "preference" => 0
+ }]
+ },
+ "version" => {
+ "id" => "4fda2190756c2805ebd1094cc4a089982bac615279d8288cd4a4a9fdc4337761",
+ "published_at" => 1371489957115
+ }
+ }
+ }
- let(:env) { {'rack.session' => {}} }
+ let(:app_post_id) { 'app-post-id' }
+ let(:app_post) {
+ {
+ :id => app_post_id,
+ :published_at => (Time.now.to_f * 1000).to_i,
+ :type => "https://tent.io/types/app/v0#",
+ :content => {
+ :name => "Example App Name",
+ :description => "Example App Description",
+ :url => "http://someapp.example.com",
+ :redirect_uri => "http://someapp.example.com/oauth/callback",
+ :types => {
+ :read => %w( all ),
+ :write => %w( https://tent.io/types/status/v0# )
+ },
+ :notification_types => %w( all ),
+ :scopes => %w( import_posts )
+ },
+ :permissions => {
+ :public => false
+ }
+ }
+ }
- let(:fresh_strategy){ Class.new(OmniAuth::Strategies::Tent) }
+ let(:app_credentials_post_id) { 'app-credentials-post-id' }
+ let(:server_app_credentials_post_url) { "#{server_url}/posts/#{app_credentials_post_id}" }
+ let(:app_credentials_post) {
+ {
+ :id => app_credentials_post_id,
+ :published_at => (Time.now.to_f * 1000).to_i,
+ :type => "https://tent.io/types/credentials/v0#",
+ :content => {
+ :hawk_key => 'hawk-mac-key',
+ :hawk_algorithm => 'sha256'
+ },
+ :permissions => {
+ :public => false
+ }
+ }
+ }
- let(:tent_entity) { 'https://example.com' }
- let(:tent_server) { "#{tent_entity}/tent" }
- let(:app_id) { 'app-id-123' }
- let(:link_header) { %(<#{tent_server}/profile>; rel="%s") % TentClient::PROFILE_REL }
- let(:tent_profile) { %({"https://tent.io/types/info/core/v0.1.0":{"licenses":["http://creativecommons.org/licenses/by/3.0/"],"entity":"#{tent_entity}","servers":["#{tent_server}"]}}) }
- let(:app_attrs) do
+ let(:app_credentials) {
+ app_credentials_post[:content].merge(:hawk_id => app_credentials_post_id)
+ }
+
+ let(:access_token_hash) {
{
- :name => "Example App",
- :description => "An example app",
- :scopes => { "read_posts" => "Display your posts feed" },
- :icon => "https://example.com/icon.png",
- :url => "https://example.com"
+ :access_token => 'app-auth-id',
+ :hawk_key => 'hawk-mac-key',
+ :hawk_algorithm => 'sha256',
+ :token_type => 'hawk'
}
+ }
+
+ def server_named_url(name, params = {})
+ uri_template = meta_post['content']['servers'].first['urls'][name.to_s]
+ uri_template.gsub(/\{([^\}]+)\}/) { URI.encode_www_form_component(params[$1.to_sym]) || "#{$1}" }
end
- let(:app_json) { %({"name":"Example App","id":"#{app_id}"}) }
- let(:app_hash) { Yajl::Parser.parse(app_json) }
- let(:token_code) { 'token-code-123abc' }
+ def stub_head_discovery!
+ stub_request(:head, entity_uri).to_return(:headers => { 'Link' => link_header })
+ end
- let(:access_token) { 'access-token-abc' }
- let(:mac_key) { 'mac-key-312' }
- let(:mac_algorithm) { 'hmac-sha-256' }
- let(:token_type) { 'mac' }
- let(:app_auth_json) { %({"access_token":"#{access_token}","mac_key":"#{mac_key}","mac_algorithm":"#{mac_algorithm}","token_type":"#{token_type}") }
+ def stub_meta_discovery!
+ stub_request(:any, server_meta_post_url).to_return(
+ :status => 200,
+ :headers => {
+ 'Content-Type' => 'application/json'
+ },
+ :body => Yajl::Encoder.encode(:post => meta_post)
+ )
+ end
- let(:stub_head_discovery!) do
- stub_request(:head, tent_entity).to_return(:headers => {'Link' => link_header})
+ def stub_app_create!
+ stub_request(:post, server_named_url(:new_post)).to_return(
+ :status => 200,
+ :headers => {
+ 'Content-Type' => 'application/json',
+ 'Link' => %(<#{server_app_credentials_post_url}>; rel="https://tent.io/rels/credentials")
+ },
+ :body => Yajl::Encoder.encode(:post => app_post)
+ )
end
- let(:stub_profile_discovery!) do
- stub_request(:get, "#{tent_server}/profile").to_return(:body => tent_profile, :headers => {'Content-Type' => TentClient::MEDIA_TYPE})
+ def stub_fetch_app!
+ stub_request(:get, server_named_url(:post, :entity => entity_uri, :post => app_post_id)).to_return(
+ :status => 200,
+ :headers => {
+ 'Content-Type' => 'application/json',
+ },
+ :body => Yajl::Encoder.encode(:post => app_post)
+ )
end
- let(:stub_app_lookup_success!) do
- stub_request(:get, "#{tent_server}/apps/#{app_id}").to_return(:body => app_json, :headers => { 'Content-Type' => TentClient::MEDIA_TYPE })
+ def stub_fetch_app_failure!
+ stub_request(:get, server_named_url(:post, :entity => entity_uri, :post => app_post_id)).to_return(
+ :status => 404,
+ :headers => {
+ 'Content-Type' => 'application/json',
+ },
+ :body => Yajl::Encoder.encode(:error => 'Not Found')
+ )
end
- let(:stub_app_lookup_failure!) do
- stub_request(:get, "#{tent_server}/apps/#{app_id}").to_return(:status => 404)
+ def stub_fetch_app_credentials!
+ stub_request(:get, server_app_credentials_post_url).to_return(
+ :status => 200,
+ :headers => {
+ 'Content-Type' => 'application/json',
+ },
+ :body => Yajl::Encoder.encode(:post => app_credentials_post)
+ )
end
- let(:stub_app_create_success!) do
- stub_request(:post, "#{tent_server}/apps").to_return(:body => app_json, :headers => { 'Content-Type' => TentClient::MEDIA_TYPE })
+ def stub_app_auth_create!
+ stub_request(:post, server_named_url(:oauth_token)).to_return(
+ :status => 200,
+ :headers => {
+ 'Content-Type' => 'application/json'
+ },
+ :body => Yajl::Encoder.encode(access_token_hash)
+ )
end
- let(:stub_app_auth_create_success!) do
- stub_request(:post, "#{tent_server}/apps/#{app_id}/authorizations").with(:body => Yajl::Encoder.encode({ :code => token_code })).to_return(:body => app_auth_json, :headers => { 'Content-Type' => TentClient::MEDIA_TYPE })
+ let(:env) { {'rack.session' => {}} }
+
+ let(:app_attrs) do
+ {
+ :name => app_post[:content][:name],
+ :description => app_post[:content][:description],
+ :url => app_post[:content][:url],
+ :redirect_uri => app_post[:content][:redirect_uri],
+ :read_types => app_post[:content][:types][:read],
+ :write_types => app_post[:content][:types][:write],
+ :notification_types => app_post[:content][:notification_types],
+ :notification_url => app_post[:content][:notification_url],
+ :scopes => app_post[:content][:scopes],
+ }
end
describe '#request_phase' do
- it 'should display a form' do
+ it 'displays a form' do
get '/auth/tent', {}, env
expect(last_response.body).to be_include("<form")
end
- it 'should perform disvocery' do
+ it 'performs disvocery' do
head_stub = stub_head_discovery!
- profile_stub = stub_profile_discovery!
+ meta_stub = stub_meta_discovery!
described_class.any_instance.stubs(:find_or_create_app!)
described_class.any_instance.stubs(:build_uri_and_redirect!).returns([200, {}, []])
- post '/auth/tent', { :entity => tent_entity }, env
+ post '/auth/tent', { :entity => entity_uri }, env
expect(head_stub).to have_been_requested
- expect(profile_stub).to have_been_requested
+ expect(meta_stub).to have_been_requested
end
- it 'should create app if app_id callback returns nil' do
- set_app!(:app => app_attrs)
- stub_head_discovery!
- stub_profile_discovery!
- app_create_stub = stub_app_create_success!
- described_class.any_instance.stubs(:build_uri_and_redirect!).returns([200, {}, []])
+ creates_app = proc do
+ it 'creates app' do
+ stub_head_discovery!
+ stub_meta_discovery!
+ app_create_stub = stub_app_create!
+ fetch_credentials_stub = stub_fetch_app_credentials!
- post '/auth/tent', { :entity => tent_entity }, env
+ described_class.any_instance.stubs(:build_uri_and_redirect!).returns([200, {}, []])
- expect(app_create_stub).to have_been_requested
+ post '/auth/tent', { :entity => entity_uri }, env
+
+ expect(app_create_stub).to have_been_requested
+ expect(fetch_credentials_stub).to have_been_requested
+ end
end
- it 'should create app if not found' do
- set_app!(:app => app_attrs, :on_app_created => mock(:call))
- stub_head_discovery!
- stub_profile_discovery!
- stub_app_lookup_failure!
- app_create_stub = stub_app_create_success!
- described_class.any_instance.stubs(:build_uri_and_redirect!).returns([200, {}, []])
+ builds_uri_and_redirects = proc do
+ it 'builds uri and redirects' do
+ stub_head_discovery!
+ stub_meta_discovery!
+ stub_fetch_app!
- post '/auth/tent', { :entity => tent_entity }, env
+ post '/auth/tent', { :entity => entity_uri }, env
- expect(app_create_stub).to have_been_requested
+ expect(last_response.status).to eq(302)
+ expect(last_response.headers["Location"]).to match(%r{\A#{Regexp.escape(server_named_url(:oauth_auth))}})
+ expect(last_response.headers["Location"]).to match(%r{client_id=#{app_post_id}})
+ end
end
- it 'should build uri and redirect' do
- set_app!(:get_app => lambda { |entity| app_hash })
- stub_head_discovery!
- stub_profile_discovery!
- stub_app_lookup_success!
+ context 'when app_id proc returns nil' do
+ before do
+ set_app(:app => app_attrs)
+ end
- post '/auth/tent', { :entity => tent_entity }, env
+ context &creates_app
+ end
- expect(last_response.status).to eq(302)
- expect(last_response.headers["Location"]).to match(%r{^#{tent_server}/oauth/authorize})
- expect(last_response.headers["Location"]).to match(%r{client_id=#{app_id}})
+ context 'when app not found' do
+ context 'when lookup fails' do
+ before do
+ set_app(:app => app_attrs, :get_app => lambda { |entity| })
+ end
+
+ context &creates_app
+ end
+
+ context 'when fetch fails' do
+ before do
+ set_app(:app => app_attrs, :get_app => lambda { |entity| app_post.merge(:credentials => app_credentials) })
+ stub_fetch_app_failure!
+ end
+
+ context &creates_app
+ end
end
+
+ context 'when app found' do
+ before do
+ set_app(:app => app_attrs, :get_app => lambda { |entity| app_post.merge(:credentials => app_credentials) })
+ end
+
+ context &builds_uri_and_redirects
+ end
end
- describe '#callback_phase' do
- it 'should create app authorization' do
- state = 'abcdef'
- session = {}
+ describe "#callback_phase" do
+ let(:state) { 'request-state' }
+ let(:session) { Hash.new }
+
+ let(:token_code) { 'token-code' }
+
+ before do
session['omniauth.state'] = state
- session['omniauth.entity'] = tent_entity
- session['omniauth.server_url'] = tent_server
- session['omniauth.app'] = { :id => app_id }
- session['omniauth.profile'] = Yajl::Parser.parse(tent_profile)
+ session['omniauth.entity'] = entity_uri
+ session['omniauth.server'] = meta_post['content']['servers'].first
+ end
- stub_app_auth_create_success!
- stub_app_lookup_success!
+ it 'creates app authorization' do
+ app = app_post.merge(:credentials => app_credentials)
+ set_app(:app => app_attrs, :get_app => proc { |e| app })
+ stub_head_discovery!
+ stub_meta_discovery!
+ create_auth_stub = stub_app_auth_create!
+
get '/auth/tent/callback', { :code => token_code, :state => state }, 'rack.session' => session
+ expect(create_auth_stub).to have_been_requested
+
auth_hash = last_response['env']['omniauth.auth']
- expect(auth_hash).to_not be_nil
- expect(auth_hash.provider).to eq('tent')
- expect(auth_hash.uid).to eq(tent_entity)
- expect(auth_hash.info).to eq(Hashie::Mash.new(
- :name => nil,
- :nickname => tent_entity,
- :image => nil
+ expect(auth_hash).to be_kind_of(Hashie::Mash)
+
+ expect(auth_hash.provider).to eql('tent')
+ expect(auth_hash.uid).to eql(entity_uri)
+ expect(auth_hash.credentials).to eql(Hashie::Mash.new(
+ :token => access_token_hash[:access_token],
+ :secret => access_token_hash[:hawk_key]
))
- expect(auth_hash.credentials).to eq(Hashie::Mash.new(
- :token => access_token,
- :secret => mac_key
+
+ expect(auth_hash.extra).to be_kind_of(Hashie::Mash)
+ expect(auth_hash.extra.credentials).to eql(Hashie::Mash.new(
+ :id => access_token_hash[:access_token],
+ :hawk_key => access_token_hash[:hawk_key],
+ :hawk_algorithm => access_token_hash[:hawk_algorithm],
+ :token_type => access_token_hash[:token_type]
))
- expect(auth_hash.extra.raw_info.profile).to eq(Hashie::Mash.new(Yajl::Parser.parse(tent_profile)))
- expect(auth_hash.extra.credentials).to eq(Hashie::Mash.new(
- :mac_key_id => access_token,
- :mac_key => mac_key,
- :mac_algorithm => mac_algorithm,
- :token_type => token_type
+
+ expect(auth_hash.extra.raw_info).to be_kind_of(Hashie::Mash)
+ expect(auth_hash.extra.raw_info.auth_credentials).to eql(Hashie::Mash.new(access_token_hash))
+ expect(auth_hash.extra.raw_info.app).to eql(Hashie::Mash.new(app_post.merge(:credentials => app_credentials)))
+ end
+ end
+
+ describe "full flow" do
+ let(:state) { 'request-state' }
+ let(:token_code) { 'token-code' }
+
+ before do
+ described_class.any_instance.stubs(:generate_state).returns(state)
+ end
+
+ it "maintains state through full oauth flow" do
+ app = nil
+ set_app(:app => app_attrs, :on_app_created => proc { |a, e| app = a }, :get_app => proc { |e| app })
+
+ ##
+ # Request Phase
+
+ stub_head_discovery!
+ stub_meta_discovery!
+ app_create_stub = stub_app_create!
+ fetch_credentials_stub = stub_fetch_app_credentials!
+
+ post '/auth/tent', { :entity => entity_uri }, env
+
+ expect(last_response.status).to eq(302)
+ expect(last_response.headers["Location"]).to match(%r{\A#{Regexp.escape(server_named_url(:oauth_auth))}})
+ expect(last_response.headers["Location"]).to match(%r{client_id=#{app_post_id}})
+
+ ##
+ # Callback Phase
+
+ create_auth_stub = stub_app_auth_create!
+ get '/auth/tent/callback', { :code => token_code, :state => state }, env
+
+ expect(create_auth_stub).to have_been_requested
+
+ auth_hash = last_response['env']['omniauth.auth']
+ expect(auth_hash).to be_kind_of(Hashie::Mash)
+
+ expect(auth_hash.provider).to eql('tent')
+ expect(auth_hash.uid).to eql(entity_uri)
+ expect(auth_hash.credentials).to eql(Hashie::Mash.new(
+ :token => access_token_hash[:access_token],
+ :secret => access_token_hash[:hawk_key]
))
- expect(auth_hash.extra.raw_info.app_authorization).to eq(Hashie::Mash.new(
- Yajl::Parser.parse(app_auth_json)
+
+ expect(auth_hash.extra).to be_kind_of(Hashie::Mash)
+ expect(auth_hash.extra.credentials).to eql(Hashie::Mash.new(
+ :id => access_token_hash[:access_token],
+ :hawk_key => access_token_hash[:hawk_key],
+ :hawk_algorithm => access_token_hash[:hawk_algorithm],
+ :token_type => access_token_hash[:token_type]
))
+
+ expect(auth_hash.extra.raw_info).to be_kind_of(Hashie::Mash)
+ expect(auth_hash.extra.raw_info.auth_credentials).to eql(Hashie::Mash.new(access_token_hash))
+ expect(auth_hash.extra.raw_info.app).to eql(Hashie::Mash.new(app_post.merge(:credentials => app_credentials)))
end
end
end