spec/omniauth/strategies/auth0_spec.rb in omniauth-auth0-1.4.2 vs spec/omniauth/strategies/auth0_spec.rb in omniauth-auth0-2.0.0

- old
+ new

@@ -1,177 +1,289 @@ -require "spec_helper" +require 'spec_helper' -describe OmniAuth::Strategies::Auth0 do - let(:app){ Rack::Builder.new do |b| - b.use Rack::Session::Cookie, {:secret => "abc123"} - b.run lambda{|env| [200, {}, ['Not Found']]} - end.to_app } +RSpec.shared_examples 'site has valid domain url' do |url| + it { expect(subject.site).to eq(url) } +end - before :each do - OmniAuth.config.test_mode = true - @request = double('Request') - allow(@request).to receive(:params) - allow(@request).to receive(:cookies) - allow(@request).to receive(:env) - - @session = double('Session') - allow(@session).to receive(:delete).with('omniauth.state').and_return('state') +describe OmniAuth::Strategies::Auth0 do + let(:client_id) { 'CLIENT_ID' } + let(:client_secret) { 'CLIENT_SECRET' } + let(:domain_url) { 'https://samples.auth0.com' } + let(:application) do + lambda do + [200, {}, ['Hello.']] + end end - - after do - OmniAuth.config.test_mode = false + let(:auth0) do + OmniAuth::Strategies::Auth0.new( + application, + client_id, + client_secret, + domain_url + ) end - subject do - OmniAuth::Strategies::Auth0.new(app, - "client_id", "client_secret", "tenny.auth0.com:3000").tap do |strategy| - allow(strategy).to receive(:request) { @request } + describe 'client_options' do + let(:subject) { auth0.client } + + context 'domain with https' do + let(:domain_url) { 'https://samples.auth0.com' } + it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' end - end - context "initiation" do - let(:base64_token) { - Base64.urlsafe_encode64('{"name":"omniauth-auth0","version":"' + OmniAuth::Auth0::VERSION + '"}') - } - - it "uses the correct site" do - expect(subject.options.client_options.site).to eql "https://tenny.auth0.com:3000" + context 'domain with http' do + let(:domain_url) { 'http://mydomain.com' } + it_behaves_like 'site has valid domain url', 'http://mydomain.com' end - it "uses the correct authorize_url" do - expect(subject.options.client_options.authorize_url). - to eql "https://tenny.auth0.com:3000/authorize?auth0Client=#{base64_token}" - + context 'domain with host only' do + let(:domain_url) { 'samples.auth0.com' } + it_behaves_like 'site has valid domain url', 'https://samples.auth0.com' end - it "uses the correct token_url" do - expect(subject.options.client_options.token_url). - to eql "https://tenny.auth0.com:3000/oauth/token?auth0Client=#{base64_token}" + it 'should have correct authorize path' do + expect(subject.options[:authorize_url]).to eq('/authorize') end - it "uses the correct userinfo url" do - expect(subject.options.client_options.userinfo_url). - to eql "https://tenny.auth0.com:3000/userinfo" + it 'should have the correct userinfo path' do + expect(subject.options[:userinfo_url]).to eq('/userinfo') end - it "should raise an ArgumentError error if no namespace passed" do - expect { - OmniAuth::Strategies::Auth0.new(app, "client_id", "client_secret") - }.to raise_error(ArgumentError) + it 'should have the correct token path' do + expect(subject.options[:token_url]).to eq('/oauth/token') end end - context "request phase" do - before(:each){ get '/auth/auth0' } + describe 'options' do + let(:subject) { auth0.options } - it "authenticate" do - expect(last_response.status).to eq(200) + it 'should have the correct client_id' do + expect(subject[:client_id]).to eq(client_id) end - it "authorize params" do - allow(subject).to receive(:request) { double('Request', {:params => { - "connection" => "google-oauth2", "redirect_uri" => "redirect_uri" }, :env => {}}) } - expect(subject.authorize_params).to include("connection") - expect(subject.authorize_params).to include("state") - expect(subject.authorize_params).to include("redirect_uri") + it 'should have the correct client secret' do + expect(subject[:client_secret]).to eq(client_secret) end + it 'should have correct domain' do + expect(subject[:domain]).to eq(domain_url) + end end - describe "callback phase" do - before :each do - @raw_info = { - "_id" => "165dabb5140ee2cc66b5137912ccd760", - "email" => "user@mail.com", - "family_name" => "LastName", - "gender" => "male", - "given_name" => "FirstName", - "identities" => [ - { - "access_token" => "ya29.AHES6ZRPK1Skc_rtB30Em_5RkZlKez3FkktcmJ_0RX5fIkCbkOCrXA", - "provider" => "google-oauth2", - "user_id" => "102835921788417079450", - "connection" => "google-oauth2", - "isSocial" => true - } - ], - "locale" => "en", - "name" => "FirstName LastName", - "nickname" => "nick", - "picture" => "pic", - "user_id" => "google-oauth2|102835921788417079450" - } - allow(subject).to receive(:raw_info) { @raw_info } + describe 'oauth' do + it 'redirects to hosted login page' do + get 'auth/auth0' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to start_with('https://samples.auth0.com/authorize') + expect(redirect_url).to have_query('response_type', 'code') + expect(redirect_url).to have_query('state') + expect(redirect_url).to have_query('client_id') + expect(redirect_url).to have_query('redirect_uri') end - context "info" do - it 'returns the uid (required)' do - expect(subject.uid).to eq('google-oauth2|102835921788417079450') - end + describe 'callback' do + let(:access_token) { 'access token' } + let(:expires_in) { 2000 } + let(:token_type) { 'bearer' } + let(:refresh_token) { 'refresh token' } + let(:id_token) { 'id token' } - it 'returns the name (required)' do - expect(subject.info[:name]).to eq('FirstName LastName') + let(:user_id) { 'user identifier' } + let(:state) { SecureRandom.hex(8) } + let(:name) { 'John' } + let(:nickname) { 'J' } + let(:picture) { 'some picture url' } + let(:email) { 'mail@mail.com' } + let(:email_verified) { true } + + let(:oauth_response) do + { + access_token: access_token, + expires_in: expires_in, + token_type: token_type + } end - it 'returns the email' do - expect(subject.info[:email]).to eq('user@mail.com') + let(:oidc_response) do + { + id_token: id_token, + access_token: access_token, + expires_in: expires_in, + token_type: token_type + } end - it 'returns the nickname' do - expect(subject.info[:nickname]).to eq('nick') + let(:basic_user_info) { { sub: user_id } } + let(:oidc_user_info) do + { + sub: user_id, + name: name, + nickname: nickname, + email: email, + picture: picture, + email_verified: email_verified + } end - it 'returns the last name' do - expect(subject.info[:last_name]).to eq('LastName') + def stub_auth(body) + stub_request(:post, 'https://samples.auth0.com/oauth/token') + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: MultiJson.encode(body) + ) end - it 'returns the first name' do - expect(subject.info[:first_name]).to eq('FirstName') + def stub_userinfo(body) + stub_request(:get, 'https://samples.auth0.com/userinfo') + .to_return( + headers: { 'Content-Type' => 'application/json' }, + body: MultiJson.encode(body) + ) end - it 'returns the location' do - expect(subject.info[:location]).to eq('en') + def trigger_callback + get '/auth/auth0/callback', { 'state' => state }, + 'rack.session' => { 'omniauth.state' => state } end - it 'returns the image' do - expect(subject.info[:image]).to eq('pic') + before(:each) do + WebMock.reset! end - end - context "get token" do - before :each do - @access_token = double('OAuth2::AccessToken') + let(:subject) { MultiJson.decode(last_response.body) } - allow(@access_token).to receive(:token) - allow(@access_token).to receive(:expires?) - allow(@access_token).to receive(:expires_at) - allow(@access_token).to receive(:refresh_token) - allow(@access_token).to receive(:params) + context 'basic oauth' do + before do + stub_auth(oauth_response) + stub_userinfo(basic_user_info) + trigger_callback + end - allow(subject).to receive(:access_token) { @access_token } + it 'to succeed' do + expect(last_response.status).to eq(200) + end + + it 'has credentials' do + expect(subject['credentials']['token']).to eq(access_token) + expect(subject['credentials']['expires']).to be true + expect(subject['credentials']['expires_at']).to_not be_nil + end + + it 'has basic values' do + expect(subject['provider']).to eq('auth0') + expect(subject['uid']).to eq(user_id) + expect(subject['info']['name']).to eq(user_id) + end end - it 'returns a Hash' do - expect(subject.credentials).to be_a(Hash) + context 'basic oauth w/refresh token' do + before do + stub_auth(oauth_response.merge(refresh_token: refresh_token)) + stub_userinfo(basic_user_info) + trigger_callback + end + + it 'to succeed' do + expect(last_response.status).to eq(200) + end + + it 'has credentials' do + expect(subject['credentials']['token']).to eq(access_token) + expect(subject['credentials']['refresh_token']).to eq(refresh_token) + expect(subject['credentials']['expires']).to be true + expect(subject['credentials']['expires_at']).to_not be_nil + end end - it 'returns the token' do - allow(@access_token).to receive(:token) { - { - :access_token => "OTqSFa9zrh0VRGAZHH4QPJISCoynRwSy9FocUazuaU950EVcISsJo3pST11iTCiI", - :token_type => "bearer" - } } - expect(subject.credentials['token'][:access_token]).to eq('OTqSFa9zrh0VRGAZHH4QPJISCoynRwSy9FocUazuaU950EVcISsJo3pST11iTCiI') - expect(subject.credentials['token'][:token_type]).to eq('bearer') + context 'oidc' do + before do + stub_auth(oidc_response) + stub_userinfo(oidc_user_info) + trigger_callback + end + + it 'to succeed' do + expect(last_response.status).to eq(200) + end + + it 'has credentials' do + expect(subject['credentials']['token']).to eq(access_token) + expect(subject['credentials']['expires']).to be true + expect(subject['credentials']['expires_at']).to_not be_nil + expect(subject['credentials']['id_token']).to eq(id_token) + end + + it 'has basic values' do + expect(subject['provider']).to eq('auth0') + expect(subject['uid']).to eq(user_id) + end + + it 'has info' do + expect(subject['info']['name']).to eq(name) + expect(subject['info']['nickname']).to eq(nickname) + expect(subject['info']['image']).to eq(picture) + expect(subject['info']['email']).to eq(email) + end + + it 'has extra' do + expect(subject['extra']['raw_info']['email_verified']).to be true + end end - - it 'returns the refresh token' do - allow(@access_token).to receive(:refresh_token) { "your_refresh_token" } - allow(@access_token).to receive(:params) { - { - 'id_token' => "your_id_token", - 'token_type' => "your_token_type" - } } - expect(subject.credentials['refresh_token']).to eq('your_refresh_token') - end end + end + + describe 'error_handling' do + it 'fails when missing client_id' do + @app = make_application(client_id: nil) + get 'auth/auth0' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to fail_auth_with('missing_client_id') + end + + it 'fails when missing client_secret' do + @app = make_application(client_secret: nil) + get 'auth/auth0' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to fail_auth_with('missing_client_secret') + end + + it 'fails when missing domain' do + @app = make_application(domain: nil) + get 'auth/auth0' + expect(last_response.status).to eq(302) + redirect_url = last_response.headers['Location'] + expect(redirect_url).to fail_auth_with('missing_domain') + end + end +end + +RSpec::Matchers.define :fail_auth_with do |message| + match do |actual| + uri = URI(actual) + query = CGI.parse(uri.query) + (uri.path == '/auth/failure') && + (query['message'] == [message]) && + (query['strategy'] == ['auth0']) + end +end + +RSpec::Matchers.define :have_query do |key, value| + match do |actual| + uri = redirect_uri(actual) + query = query(uri) + if value.nil? + query[key].length == 1 + else + query[key] == [value] + end + end + + def redirect_uri(string) + URI(string) + end + + def query(uri) + CGI.parse(uri.query) end end