# Copyright (C) 2010 Google Inc.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
require 'spec_helper'
require 'signet/oauth_2/client'
require 'openssl'
require 'jwt'
require 'date'

conn = Faraday.default_connection

def build_json_response(payload)
  [200, { "Content-Type" => "application/json; charset=utf-8" }, MultiJson.dump(payload)]
end

def build_form_encoded_response(payload)
  [200, { "Content-Type" => "application/json; charset=utf-8" }, Addressable::URI.form_encode(payload)]
end

describe Signet::OAuth2::Client, 'unconfigured' do
  before do
    @client = Signet::OAuth2::Client.new
  end
  it 'should allow additional paraemters to be set.' do
    @client.additional_parameters['type'] = 'web_server'
    expect(@client.additional_parameters).to eq({'type' => 'web_server'})
  end
  it 'should raise an error if a bogus scope is provided' do
    expect(lambda do
      @client = Signet::OAuth2::Client.new(:scope => :bogus)
    end).to raise_error(TypeError)
  end

  it 'should raise an error if a scope array is provided with spaces' do
    expect(lambda do
      @client = Signet::OAuth2::Client.new(:scope => [
        'legit',
        'bogus bogus'
      ])
    end).to raise_error(ArgumentError)
  end

  it 'should allow the scope to be set to a String' do
    @client.scope = 'legit'
    expect(@client.scope).to eq ['legit']
    @client.scope = 'legit alsolegit'
    expect(@client.scope).to eq ['legit', 'alsolegit']
  end

  it 'should allow the scope to be set to an Array' do
    @client.scope = ['legit']
    expect(@client.scope).to eq ['legit']
    @client.scope = ['legit', 'alsolegit']
    expect(@client.scope).to eq ['legit', 'alsolegit']
  end

  it 'should raise an error if a bogus redirect URI is provided' do
    expect(lambda do
      @client = Signet::OAuth2::Client.new(:redirect_uri => :bogus)
    end).to raise_error(TypeError)
  end

  it 'should raise an error if a relative redirect URI is provided' do
    expect(lambda do
      @client = Signet::OAuth2::Client.new(:redirect_uri => '/relative/path')
    end).to raise_error(ArgumentError)
  end

  it 'should allow "postmessage" as a redirect URI (Google hack)' do
    @client.authorization_uri = 'https://example.com/authorize'
    @client.client_id = 's6BhdRkqt3'
    @client.redirect_uri = 'postmessage'
    expect(@client.authorization_uri.query_values['redirect_uri']).to eq 'postmessage'
  end

  it 'should allow oob values as a redirect URI (for installed apps)' do
    @client.authorization_uri = 'https://example.com/authorize'
    @client.client_id = 's6BhdRkqt3'
    @client.redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
    expect(@client.authorization_uri.query_values['redirect_uri']).to eq 'urn:ietf:wg:oauth:2.0:oob'
    @client.redirect_uri = 'oob'
    expect(@client.authorization_uri.query_values['redirect_uri']).to eq 'oob'
  end

  it 'should have no authorization_uri' do
    expect(@client.authorization_uri).to eq nil
  end

  it 'should allow the authorization_uri to be set to a String' do
    @client.authorization_uri = 'https://example.com/authorize'
    @client.client_id = 's6BhdRkqt3'
    @client.redirect_uri = 'https://example.client.com/callback'
    expect(@client.authorization_uri.to_s).to include(
      'https://example.com/authorize'
    )
    expect(@client.authorization_uri.query_values['client_id']).to eq 's6BhdRkqt3'
    expect(@client.authorization_uri.query_values['redirect_uri']).to eq (
      'https://example.client.com/callback'
    )
  end

  it 'should allow the authorization_uri to be set to a Hash' do
    @client.authorization_uri = {
      :scheme => 'https', :host => 'example.com', :path => '/authorize'
    }
    @client.client_id = 's6BhdRkqt3'
    @client.redirect_uri = 'https://example.client.com/callback'
    expect(@client.authorization_uri.to_s).to include(
      'https://example.com/authorize'
    )
    expect(@client.authorization_uri.query_values['client_id']).to eq 's6BhdRkqt3'
    expect(@client.authorization_uri.query_values['redirect_uri']).to eq (
      'https://example.client.com/callback'
    )
  end

  it 'should allow the authorization_uri to be set to a URI' do
    @client.authorization_uri =
      Addressable::URI.parse('https://example.com/authorize')
    @client.client_id = 's6BhdRkqt3'
    @client.redirect_uri =
      Addressable::URI.parse('https://example.client.com/callback')
    expect(@client.authorization_uri.to_s).to include(
      'https://example.com/authorize'
    )
    expect(@client.authorization_uri.query_values['client_id']).to eq 's6BhdRkqt3'
    expect(@client.authorization_uri.query_values['redirect_uri']).to eq (
      'https://example.client.com/callback'
    )
  end

  it 'should require a redirect URI when getting the authorization_uri' do
    @client.authorization_uri =
      Addressable::URI.parse('https://example.com/authorize')
    @client.client_id = 's6BhdRkqt3'
    expect(lambda do
      @client.authorization_uri
    end).to raise_error(ArgumentError)
  end

  it 'should require a client ID when getting the authorization_uri' do
    @client.authorization_uri =
      Addressable::URI.parse('https://example.com/authorize')
    @client.redirect_uri =
      Addressable::URI.parse('https://example.client.com/callback')
    expect(lambda do
      @client.authorization_uri
    end).to raise_error(ArgumentError)
  end

  it 'should have no token_credential_uri' do
    expect(@client.token_credential_uri).to eq nil
  end

  it 'should allow the token_credential_uri to be set to a String' do
    @client.token_credential_uri = "https://example.com/token"
    expect(@client.token_credential_uri.to_s).to eq "https://example.com/token"
  end

  it 'should allow the token_credential_uri to be set to a Hash' do
    @client.token_credential_uri = {
      :scheme => 'https', :host => 'example.com', :path => '/token'
    }
    expect(@client.token_credential_uri.to_s).to eq 'https://example.com/token'
  end

  it 'should allow the token_credential_uri to be set to a URI' do
    @client.token_credential_uri =
      Addressable::URI.parse("https://example.com/token")
    expect(@client.token_credential_uri.to_s).to eq "https://example.com/token"
  end
end

describe Signet::OAuth2::Client, 'configured for assertions profile' do

  describe 'when using RSA keys' do
    before do
      @key = OpenSSL::PKey::RSA.new 2048
      @client = Signet::OAuth2::Client.new(
        :token_credential_uri =>
          'https://oauth2.googleapis.com/token',
        :scope => 'https://www.googleapis.com/auth/userinfo.profile',
        :issuer => 'app@example.com',
        :audience => 'https://oauth2.googleapis.com/token',
        :signing_key => @key
      )
    end

    it 'should generate valid JWTs' do
      jwt = @client.to_jwt
      expect(jwt).not_to be_nil

      claim, header = JWT.decode(jwt, @key.public_key, true, algorithm: 'RS256')
      expect(claim["iss"]).to eq 'app@example.com'
      expect(claim["scope"]).to eq 'https://www.googleapis.com/auth/userinfo.profile'
      expect(claim["aud"]).to eq 'https://oauth2.googleapis.com/token'
    end

    it 'should generate valid JWTs for impersonation' do
      @client.principal = 'user@example.com'
      jwt = @client.to_jwt
      expect(jwt).not_to be_nil

      claim, header = JWT.decode(jwt, @key.public_key, true, algorithm: 'RS256')
      expect(claim["iss"]).to eq 'app@example.com'
      expect(claim["prn"]).to eq 'user@example.com'
      expect(claim["scope"]).to eq 'https://www.googleapis.com/auth/userinfo.profile'
      expect(claim["aud"]).to eq 'https://oauth2.googleapis.com/token'
    end

    it 'should generate valid JWTs for impersonation using deprecated person attribute' do
      @client.person = 'user@example.com'
      jwt = @client.to_jwt
      expect(jwt).not_to be_nil

      claim, header = JWT.decode(jwt, @key.public_key, true, algorithm: 'RS256')
      expect(claim["iss"]).to eq 'app@example.com'
      expect(claim["prn"]).to eq 'user@example.com'
      expect(claim["scope"]).to eq 'https://www.googleapis.com/auth/userinfo.profile'
      expect(claim["aud"]).to eq 'https://oauth2.googleapis.com/token'
    end

    it 'should generate valid JWTs for impersonation using the sub attribute' do
      @client.sub = 'user@example.com'
      jwt = @client.to_jwt
      expect(jwt).not_to be_nil

      claim, header = JWT.decode(jwt, @key.public_key, true, algorithm: 'RS256')
      expect(claim["iss"]).to eq 'app@example.com'
      expect(claim["sub"]).to eq 'user@example.com'
      expect(claim["scope"]).to eq 'https://www.googleapis.com/auth/userinfo.profile'
      expect(claim["aud"]).to eq 'https://oauth2.googleapis.com/token'
    end

    it 'should generate a JSON representation of the client' do
      @client.principal = 'user@example.com'
      json = @client.to_json
      expect(json).not_to be_nil

      deserialized = MultiJson.load(json)
      expect(deserialized["token_credential_uri"]).to eq 'https://oauth2.googleapis.com/token'
      expect(deserialized["scope"]).to eq ['https://www.googleapis.com/auth/userinfo.profile']
      expect(deserialized["issuer"]).to eq 'app@example.com'
      expect(deserialized["audience"]).to eq 'https://oauth2.googleapis.com/token'
      expect(deserialized["signing_key"]).to eq @key.to_s
    end

    it 'should send valid access token request' do
      stubs = Faraday::Adapter::Test::Stubs.new do |stub|
        stub.post('/token') do |env|
          params = Addressable::URI.form_unencode(env[:body])
          claim, header = JWT.decode(params.assoc("assertion").last, @key.public_key, true, algorithm: 'RS256')
          expect(params.assoc("grant_type")).to eq ['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer']
          build_json_response({
            "access_token" => "1/abcdef1234567890",
            "token_type" => "Bearer",
            "expires_in" => 3600
          })
        end
      end
      connection = Faraday.new(:url => 'https://www.google.com') do |builder|
        builder.adapter(:test, stubs)
      end

      @client.fetch_access_token!(:connection => connection)
      expect(@client.access_token).to eq "1/abcdef1234567890"
      stubs.verify_stubbed_calls
    end
  end

  describe 'when using shared secrets' do
    before do
      @key = 'my secret key'
      @client = Signet::OAuth2::Client.new(
        :token_credential_uri =>
          'https://oauth2.googleapis.com/token',
        :scope => 'https://www.googleapis.com/auth/userinfo.profile',
        :issuer => 'app@example.com',
        :audience => 'https://oauth2.googleapis.com/token',
        :signing_key => @key
      )
    end

    it 'should generate valid JWTs' do
      jwt = @client.to_jwt
      expect(jwt).not_to be_nil

      claim, header = JWT.decode(jwt, @key, true, algorithm: 'HS256')
      expect(claim["iss"]).to eq 'app@example.com'
      expect(claim["scope"]).to eq 'https://www.googleapis.com/auth/userinfo.profile'
      expect(claim["aud"]).to eq 'https://oauth2.googleapis.com/token'
    end
  end
end

describe Signet::OAuth2::Client, 'configured for Google userinfo API' do
  before do
    @client = Signet::OAuth2::Client.new(
      :authorization_uri =>
        'https://accounts.google.com/o/oauth2/auth',
      :token_credential_uri =>
        'https://oauth2.googleapis.com/token',
      :scope => 'https://www.googleapis.com/auth/userinfo.profile'
    )
  end

  it 'should not have a grant type by default' do
    expect(@client.grant_type).to eq nil
  end

  it 'should use the authorization_code grant type if given code' do
    @client.code = '00000'
    @client.redirect_uri = 'http://www.example.com/'
    expect(@client.grant_type).to eq 'authorization_code'
  end

  it 'should use the refresh_token grant type if given refresh token' do
    @client.refresh_token = '54321'
    expect(@client.grant_type).to eq 'refresh_token'
  end

  it 'should use the password grant type if given username and password' do
    @client.username = 'johndoe'
    @client.password = 'incognito'
    expect(@client.grant_type).to eq 'password'
  end

  it 'should allow the grant type to be set manually' do
    @client.grant_type = 'authorization_code'
    expect(@client.grant_type).to eq 'authorization_code'
    @client.grant_type = 'refresh_token'
    expect(@client.grant_type).to eq 'refresh_token'
    @client.grant_type = 'password'
    expect(@client.grant_type).to eq 'password'
  end

  it 'should allow the grant type to be set to an extension' do
    @client.grant_type = 'urn:ietf:params:oauth:grant-type:saml2-bearer'
    @client.extension_parameters['assertion'] =
      'PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU'

    expect(@client.grant_type).to eq Addressable::URI.parse('urn:ietf:params:oauth:grant-type:saml2-bearer')
    expect(@client.extension_parameters).to eq ({'assertion' => 'PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU'})
  end

  it 'should raise an error if extension parameters are bogus' do
    expect(lambda do
      @client.extension_parameters = :bogus
    end).to raise_error(TypeError)
  end

  it 'should include extension parameters in token request' do
    @client.grant_type = 'urn:ietf:params:oauth:grant-type:saml2-bearer'
    @client.extension_parameters['assertion'] =
      'PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU'

    request = @client.generate_access_token_request
    expect(request).to include('assertion' => 'PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU')
  end

  it 'should include the scope in token request' do
    @client.scope = ['https://www.googleapis.com/auth/userinfo.profile']

    request = @client.generate_access_token_request(:use_configured_scope => true)
    expect(request).to include('scope' => ['https://www.googleapis.com/auth/userinfo.profile'])
  end

  it 'should allow the token to be updated' do
    issued_at = Time.now
    @client.update_token!(
      :access_token => '12345',
      :refresh_token => '54321',
      :expires_in => 3600,
      :issued_at => issued_at
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    expect(@client.issued_at).to eq issued_at
    expect(@client).to_not be_expired
  end

  it 'should handle expires as equivalent to expires_in' do
    issued_at = Time.now
    @client.update_token!(
      :access_token => '12345',
      :refresh_token => '54321',
      :expires => 600,
      :issued_at => issued_at
    )
    expect(@client.expires_in).to eq 600
  end

  it 'should allow the token to be updated without an expiration' do
    @client.update_token!(
      :access_token => '12345',
      :refresh_token => '54321'
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq nil
    expect(@client.issued_at).to eq nil
    expect(@client).to_not be_expired
  end

  it 'should allow the token expiration to be cleared' do
    issued_at = Time.now
    @client.update_token!(
      :access_token => '12345',
      :refresh_token => '54321',
      :expires_in => 3600,
      :issued_at => issued_at
    )
    @client.expires_in = nil
    @client.issued_at = nil
    expect(@client).to_not be_expired
  end

  it 'should allow the expires_at time to be updated' do
    expires_at = Time.now
    @client.update_token!(
      :expires_at => expires_at.to_i,
      :expires_in => nil
    )
    expect(@client.expires_at).to be_within(1).of(expires_at)
    expect(@client).to be_expired
  end

  it 'should calculate the expires_at from issued_at when issued_at is set' do
    expires_in = 3600
    issued_at = Time.now - expires_in
    @client.update_token!(
      :issued_at => issued_at,
      :expires_in => expires_in
    )
    expect(@client.expires_at).to eq issued_at + expires_in
    expect(@client).to be_expired
  end

  it 'should calculate expires_at from Time.now when issed_at is NOT set' do
    expires_in = 3600
    expires_at = Time.now + expires_in
    @client.update_token! :expires_in => expires_in
    expect(@client.expires_at).to be_within(1).of(expires_at)
    expect(@client).to_not be_expired
  end

  # This test is to document the way that expires_in has always been used:
  # If expires_in is set on the client, it always resets the issued_at time
  # to Time.now
  it 'sets issued_at to Time.now when expires_in is not set through update_token!' do
    one_hour = 3600
    issued_at = Time.now - (2 * one_hour)
    current_time = Time.now

    @client.issued_at = issued_at
    @client.expires_in = one_hour

    expect(@client.issued_at).to_not eq issued_at
    expect(@client.issued_at).to be_within(1).of(current_time)
    expect(@client).to_not be_expired
  end

  it 'should allow setting expires_at manually' do
    expires_at = Time.now+100
    @client.expires_at = expires_at.to_i
    expect(@client.expires_at).to be_within(1).of(expires_at)
    expect(@client).to_not be_expired
  end

  it 'should normalize values of expires_at to instances of time' do
    time_formats = [DateTime.new, "12:00", 100, Time.new]
    normalized_time_formats = []
    time_formats.each do |time|
      @client.expires_at = time
      normalized_time_formats << @client.expires_at
    end
    normalized_time_formats.each do |time|
      expect(time).to be_an_instance_of(Time)
    end
  end

  it 'should set expires_in when expires_at is set' do
    issued_at = Time.now
    expires_at = Time.now+100
    @client.expires_at = expires_at.to_i
    @client.issued_at = issued_at
    expect(@client.expires_in).to be_within(1).of (expires_at - issued_at).to_i
    @client.expires_at = nil
    expect(@client.expires_in).to be_nil
  end

  it 'should set expires_in to nil when expires_at is set to nil' do
    @client.expires_at = nil
    expect(@client.expires_in).to be_nil
  end

  it 'should set expires_at when expires_in is set' do
    expires_in = 100
    @client.expires_in = expires_in
    expect(@client.expires_at).to eq (@client.issued_at + expires_in)
    @client.expires_in = nil
    expect(@client.expires_at).to be_nil
  end

  it 'should set expires_at to nil when expires_in is set to nil' do
    @client.expires_in = nil
    expect(@client.expires_at).to be_nil
  end

  it 'should indicate the token is not expired if expired_at nil' do
    @client.expires_at = nil
    expect(@client.expires_within?(60)).to be false
    expect(@client.expired?).to be false
  end

  it 'should indicate the token is not expiring when expiry beyond window' do
    @client.expires_at = Time.now+100
    expect(@client.expires_within?(60)).to be false
  end

  it 'should indicate the token is expiring soon when expiry within window' do
    @client.expires_at = Time.now+30
    expect(@client.expires_within?(60)).to be true
  end

  it 'should raise an error if the authorization endpoint is not secure' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.redirect_uri = 'http://www.example.com/'
    @client.authorization_uri = 'http://accounts.google.com/o/oauth2/auth'
    expect(lambda do
      @client.authorization_uri
    end).to raise_error(Signet::UnsafeOperationError)
  end

  it 'should raise an error if token credential URI is missing' do
    @client.token_credential_uri = nil
    expect(lambda do
      @client.fetch_access_token!
    end).to raise_error(ArgumentError)
  end

  it 'should raise an error if unauthorized' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        [401, {}, 'User authorization failed or something.']
      end
    end
    expect(lambda do
      connection = Faraday.new(:url => 'https://www.google.com') do |builder|
        builder.adapter(:test, stubs)
      end
      @client.fetch_access_token!(
        :connection => connection
      )
    end).to raise_error(Signet::AuthorizationError)
    stubs.verify_stubbed_calls
  end

  it 'should raise a remote server error if the server gives a 5xx status' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        [509, {}, 'Rate limit hit or something.']
      end
    end
    expect(lambda do
      connection = Faraday.new(:url => 'https://www.google.com') do |builder|
        builder.adapter(:test, stubs)
      end
      @client.fetch_access_token!(
        :connection => connection
      )
    end).to raise_error(Signet::RemoteServerError)
    stubs.verify_stubbed_calls
  end

  it 'should raise an unexpected error if the token server gives an unexpected status' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        [309, {}, 'Rate limit hit or something.']
      end
    end
    expect(lambda do
      connection = Faraday.new(:url => 'https://www.google.com') do |builder|
        builder.adapter(:test, stubs)
      end
      @client.fetch_access_token!(
        :connection => connection
      )
    end).to raise_error(Signet::UnexpectedStatusError)
    stubs.verify_stubbed_calls
  end

  it 'should correctly fetch an access token' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.code = '00000'
    @client.redirect_uri = 'https://www.example.com/'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600'
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    stubs.verify_stubbed_calls
  end

  it 'should correctly fetch an access token with a password' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.username = 'johndoe'
    @client.password = 'incognito'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600'
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    stubs.verify_stubbed_calls
  end

  it 'should correctly refresh an access token' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.refresh_token = '54321'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600'
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    stubs.verify_stubbed_calls
  end

  it 'should detect unintential grant type of none' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.redirect_uri = 'https://www.example.com/'
    expect(lambda do
      @client.fetch_access_token!
    end).to raise_error(ArgumentError)
  end

  it 'should correctly fetch protected resources' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.get('/oauth2/v1/userinfo?alt=json') do
        [200, {}, <<-JSON]
{
  "id": "116452824309856782163",
  "name": "Bob Aman",
  "given_name": "Bob",
  "family_name": "Aman",
  "link": "https://plus.google.com/116452824309856782163"
}
JSON
      end
    end
    connection = Faraday.new(:url => 'https://www.googleapis.com') do |builder|
      builder.adapter(:test, stubs)
    end
    response = @client.fetch_protected_resource(
      :connection => connection,
      :uri => 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json'
    )
    expect(response.status).to eq 200
    expect(response.body).to eq <<-JSON
{
  "id": "116452824309856782163",
  "name": "Bob Aman",
  "given_name": "Bob",
  "family_name": "Aman",
  "link": "https://plus.google.com/116452824309856782163"
}
JSON
    stubs.verify_stubbed_calls
  end

  it 'should correctly send the realm in the Authorization header' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    connection = Faraday.new(:url => 'https://www.googleapis.com') do |builder|
      builder.adapter(:test)
    end
    request = @client.generate_authenticated_request(
      :connection => connection,
      :realm => 'Example',
      :request => conn.build_request(:get) do |req|
        req.url('https://www.googleapis.com/oauth2/v1/userinfo?alt=json')
      end
    )
    expect(request.headers['Authorization']).to eq 'Bearer 12345, realm="Example"'
  end

  it 'should correctly send the realm in the Authorization header' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    connection = Faraday.new(:url => 'https://www.googleapis.com') do |builder|
      builder.adapter(:test)
    end
    request = @client.generate_authenticated_request(
      :connection => connection,
      :realm => 'Example',
      :request => [
        'GET',
        'https://www.googleapis.com/oauth2/v1/userinfo?alt=json',
        {},
        ['']
      ]
    )
    expect(request.headers['Authorization']).to eq 'Bearer 12345, realm="Example"'
  end

  it 'should not raise an error if a request is ' +
      'provided without a connection' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    request = @client.generate_authenticated_request(
      :realm => 'Example',
      :request => conn.build_request(:get) do |req|
        req.url('https://www.googleapis.com/oauth2/v1/userinfo?alt=json')
      end
    )
  end

  it 'should raise an error if not enough information ' +
      'is supplied to create a request' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    expect(lambda do
      @client.generate_authenticated_request(
        :realm => 'Example',
        :method => 'POST'
      )
    end).to raise_error(ArgumentError)
  end

  it 'should raise an error if the client does not have an access token' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    expect(lambda do
      @client.fetch_protected_resource
    end).to raise_error(ArgumentError)
  end

  it 'should not raise an error if the API server gives an error status' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.get('/oauth2/v1/userinfo?alt=json') do
        [509, {}, 'Rate limit hit or something.']
      end
    end
    connection = Faraday.new(:url => 'https://www.googleapis.com') do |builder|
      builder.adapter(:test, stubs)
    end
    response = @client.fetch_protected_resource(
      :connection => connection,
      :uri => 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json'
    )
    expect(response.status).to eq 509
    expect(response.body).to eq 'Rate limit hit or something.'
    stubs.verify_stubbed_calls
  end

  it 'should only raise an error if the API server ' +
      'gives an authorization failed status' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    @client.access_token = '12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.get('/oauth2/v1/userinfo?alt=json') do
        [401, {}, 'User authorization failed or something.']
      end
    end
    expect(lambda do
      connection = Faraday.new(
        :url => 'https://www.googleapis.com'
      ) do |builder|
        builder.adapter(:test, stubs)
      end
      @client.fetch_protected_resource(
        :connection => connection,
        :uri => 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json'
      )
    end).to raise_error(Signet::AuthorizationError)
    stubs.verify_stubbed_calls
  end

  it 'should correctly handle an ID token' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600',
          'id_token' => (
            'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD' +
            'lKN244VjBOMnZjdzZlTWlqZyIsImF1ZCI6ImNsaWVudC0xMjM0NSIsImlkIjoiM' +
            'TIzNDUiLCJpYXQiOjEzMjA2NzA5NzgsImV4cCI6MTMyMDY3NDg3OCwiY2lkIjoi' +
            'Y2xpZW50LTEyMzQ1IiwiaXNzIjoiZXhhbXBsZS5jb20ifQ.tsF3srlBaAh6pV3U' +
            'wfRrHSA3-jwnvOw6MMsQ6sO4kjc'
          )
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.decoded_id_token(nil, :verify_expiration => false)).to eq ({
      "token_hash" => "tghD9J7n8V0N2vcw6eMijg",
      "id" => "12345",
      "aud" => "client-12345",
      "iat" => 1320670978,
      "exp" => 1320674878,
      "cid" => "client-12345",
      "iss" => "example.com"
    })
    expect(@client.expires_in).to eq 3600
    stubs.verify_stubbed_calls
  end

  it 'should raise an error decoding an ID token if ' +
      'audience does not match client ID' do
    @client.client_id = 'client-54321'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600',
          'id_token' => (
            'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD' +
            'lKN244VjBOMnZjdzZlTWlqZyIsImF1ZCI6ImNsaWVudC0xMjM0NSIsImlkIjoiM' +
            'TIzNDUiLCJpYXQiOjEzMjA2NzA5NzgsImV4cCI6MTMyMDY3NDg3OCwiY2lkIjoi' +
            'Y2xpZW50LTEyMzQ1IiwiaXNzIjoiZXhhbXBsZS5jb20ifQ.tsF3srlBaAh6pV3U' +
            'wfRrHSA3-jwnvOw6MMsQ6sO4kjc'
          )
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    expect(lambda do
      @client.decoded_id_token(nil, :verify_expiration => false)
    end).to raise_error(Signet::UnsafeOperationError)
    stubs.verify_stubbed_calls
  end

  it 'should raise an error decoding an ID token if ' +
      'audience is missing' do
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600',
          'id_token' => (
            'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD' +
            'lKN244VjBOMnZjdzZlTWlqZyIsImlkIjoiMTIzNDUiLCJpYXQiOjEzMjA2NzA5N' +
            'zgsImV4cCI6MTMyMDY3NDg3OCwiY2lkIjoiY2xpZW50LTEyMzQ1IiwiaXNzIjoi' +
            'ZXhhbXBsZS5jb20ifQ.7qj85CKbQyVdDe5y2ScdJAZNkEeKMPW9LIonLxG1vu8'
          )
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    expect(lambda do
      @client.decoded_id_token(nil, :verify_expiration => false)
    end).to raise_error(Signet::UnsafeOperationError)
    stubs.verify_stubbed_calls
  end

  it 'should raise an error if the id token cannot be verified' do
    pending "Need to set test data"
    @client.client_id = 'client-12345'
    @client.client_secret = 'secret-12345'
    stubs = Faraday::Adapter::Test::Stubs.new do |stub|
      stub.post('/token') do
        build_json_response({
          'access_token' => '12345',
          'refresh_token' => '54321',
          'expires_in' => '3600',
          'id_token' => (
            'eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY' +
            'XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI' +
            'sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb' +
            '20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ' +
            '0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO' +
            'jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7' +
            'wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl' +
            '6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4'
          )
        })
      end
    end
    connection = Faraday.new(:url => 'https://www.google.com') do |builder|
      builder.adapter(:test, stubs)
    end
    @client.fetch_access_token!(
      :connection => connection
    )
    expect(@client.access_token).to eq '12345'
    expect(@client.refresh_token).to eq '54321'
    expect(@client.expires_in).to eq 3600
    expect(lambda do
      pubkey = OpenSSL::PKey::RSA.new(<<-PUBKEY)
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm
SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq
3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w
CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or
SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il
LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B
xwIDAQAB
-----END PUBLIC KEY-----
PUBKEY
      @client.decoded_id_token(pubkey)
    end).to raise_error(JWT::ExpiredSignature)
    stubs.verify_stubbed_calls
  end
end

describe Signet::OAuth2::Client, 'authorization_uri' do
  before do
    @client = Signet::OAuth2::Client.new(
      :client_id => 's6BhdRkqt3',
      :redirect_uri => 'https://example.client.com/callback',
      :authorization_uri => 'https://example.com/authorize'
    )
  end

  it 'should set access_type to offline by default' do
    expect(@client.authorization_uri.query_values['access_type']).to eq 'offline'
  end

  it 'should set response_type to code by default' do
    expect(@client.authorization_uri.query_values['response_type']).to eq 'code'
  end

  it 'should raise an error when setting both prompt and approval_prompt' do
    expect(lambda do
      @client.authorization_uri(:approval_prompt => 'force', :prompt => 'consent')
    end).to raise_error(ArgumentError)
  end
end

describe Signet::OAuth2::Client, 'configured with custom parameters' do
  before do
    @client = Signet::OAuth2::Client.new(
        :client_id => 's6BhdRkqt3',
        :redirect_uri => 'https://example.client.com/callback',
        :authorization_uri => 'https://example.com/authorize',
        :token_credential_uri => 'https://example.com/token',
        :additional_parameters =>{'type' => 'web_server'}
    )
  end

  # Normalizing to symbols - good test case example here for changes to normalized input.
  # Also tests Addressable's output.
  # Note: The only changes made here are to testing the **INTERNAL** representation of options.
  it 'should allow custom parameters to be set on init' do
    expect(@client.additional_parameters).to eq({:type => 'web_server'})
  end

  it 'should allow custom parameters to be updated' do
    @client.update!(:additional_parameters => {:type => 'new_type'})
    expect(@client.additional_parameters).to eq ({ :type => 'new_type'})
  end

  it 'should use custom parameters when generating authorization_uri' do
    expect(@client.authorization_uri().query_values).to eq ({
      "access_type"=>"offline",
      "client_id"=>"s6BhdRkqt3",
      "redirect_uri"=>"https://example.client.com/callback",
      "response_type"=>"code",
      "type"=>"web_server"})
  end

  it 'should merge new authorization_uri custom parameters' do
    expect(@client.authorization_uri(:additional_parameters => {'type' => 'new_type', 'new_param' => 'new_val'}).query_values).to eql({"access_type"=>"offline", "client_id"=>"s6BhdRkqt3", "new_param"=>"new_val",  "response_type"=>"code","redirect_uri"=>"https://example.client.com/callback", "type"=>"new_type"})
  end

  it 'should merge new generate_access_token_request custom parameters' do
    @client.update!(:code=>'12345')
    params = @client.generate_access_token_request(:additional_parameters => {'type' => 'new_type', 'new_param' => 'new_val'})
    expect(params).to include('type' => 'new_type')
    expect(params).to include('new_param' => 'new_val')
  end
end

describe Signet::OAuth2::Client, 'configured with custom parameters' do
  before do
    @client = Signet::OAuth2::Client.new(
        "client_id" => 's6BhdRkqt3',
        "redirect_uri" => 'https://example.client.com/callback',
        "authorization_uri" => 'https://example.com/authorize',
        "token_credential_uri" => 'https://example.com/token',
        "additional_parameters" => {'type' => 'web_server'}
    )
  end

  # Normalizing to symbols - good test case example here for changes to normalized input.
  # Also tests Addressable's output.
  # Note: The only changes made here are to testing the **INTERNAL** representation of options.
  it 'should allow custom parameters to be set on init' do
    expect(@client.additional_parameters).to eq ({ :type => 'web_server'})
  end

  it 'should allow custom parameters to be updated' do
    @client.update!(:additional_parameters => {'type' => 'new_type'})
    expect(@client.additional_parameters).to eql ({ :type => 'new_type'})
  end

  it 'should use custom parameters when generating authorization_uri' do
    expect(@client.authorization_uri().query_values).to eq ({"access_type"=>"offline", "client_id"=>"s6BhdRkqt3", "redirect_uri"=>"https://example.client.com/callback", "response_type"=>"code", "type"=>"web_server"})
  end

  it 'should have the correct authorization_uri' do
    expect(@client.authorization_uri.host).to eq 'example.com'
    expect(@client.authorization_uri.path).to eq '/authorize'
  end

  it 'should merge new authorization_uri custom parameters' do
    expect(@client.authorization_uri(:additional_parameters => {'type' => 'new_type', 'new_param' => 'new_val'}).query_values).to eq ({"access_type"=>"offline", "client_id"=>"s6BhdRkqt3", "new_param"=>"new_val",  "response_type"=>"code","redirect_uri"=>"https://example.client.com/callback", "type"=>"new_type"})
  end

  it 'should not have access_type parameter in authorization_uri when we set it to nil in client' do
    @client.update!(:access_type=>nil)
    expect(@client.authorization_uri().query_values).to eq ({"client_id"=>"s6BhdRkqt3", "response_type"=>"code", "redirect_uri"=>"https://example.client.com/callback"})
  end

  it 'should use new access_type parameter as default for authorization_uri' do
    @client.update!(:access_type=>:online)
    expect(@client.authorization_uri().query_values).to eq ({"access_type"=>"online", "client_id"=>"s6BhdRkqt3", "response_type"=>"code", "redirect_uri"=>"https://example.client.com/callback"})
  end

  it 'should merge new generate_access_token_request custom parameters' do
    @client.update!(:code=>'12345')
    params = @client.generate_access_token_request(:additional_parameters => {'type' => 'new_type', 'new_param' => 'new_val'})
    expect(params).to include("type" => "new_type")
    expect(params).to include("new_param" => "new_val")
  end
end

describe Signet::OAuth2::Client, 'configured with custom parameters a la JSON.load(credentials_file)' do
  before do
    @client = Signet::OAuth2::Client.new(
        "client_id" => 's6BhdRkqt3',
        "redirect_uri" => 'https://example.client.com/callback',
        "authorization_uri" => {"scheme"=>"https", "user"=>nil, "password"=>nil, "host"=>"accounts.google.com", "port"=>nil, "path"=>"/o/oauth2/auth", "query"=>nil, "fragment"=>nil},
        "token_credential_uri" => 'https://example.com/token',
        "additional_parameters" => {'type' => 'web_server'}
    )
  end

  it 'should allow custom parameters to be set on init' do
    expect(@client.additional_parameters).to eq ({:type => 'web_server'})
  end

  it 'should allow custom parameters to be updated' do
    @client.update!(:additional_parameters => {'type' => 'new_type'})
    expect(@client.additional_parameters).to eql ({:type => 'new_type'})
  end

  it 'should have correct authorization_uri hash options' do
    expect(@client.authorization_uri.host).to eq "accounts.google.com"
    expect(@client.authorization_uri.path).to eq "/o/oauth2/auth"
  end

  it 'should use custom parameters when generating authorization_uri' do
    expect(@client.authorization_uri().query_values).to eq ({"access_type"=>"offline", "client_id"=>"s6BhdRkqt3", "redirect_uri"=>"https://example.client.com/callback", "response_type"=>"code", "type"=>"web_server"})
  end

  # , "path" => "/o/oauth2/oauth", "host" => "accounts.google.com"

  it 'should merge new authorization_uri custom parameters' do
    expect(@client.authorization_uri(:additional_parameters => {'type' => 'new_type', 'new_param' => 'new_val'}).query_values).to eq ({
      "access_type"=>"offline",
      "client_id"=>"s6BhdRkqt3",
      "new_param"=>"new_val",
      "response_type"=>"code",
      "redirect_uri"=>"https://example.client.com/callback",
      "type"=>"new_type"})
  end

  it 'should merge new generate_access_token_request custom parameters' do
    @client.update!(:code=>'12345')
    params = @client.generate_access_token_request(:additional_parameters => {'type' => 'new_type', 'new_param' => 'new_val'})
    expect(params).to include("type" => "new_type")
    expect(params).to include("new_param" => "new_val")
  end
end
