require 'spec_helper'

# fails intermittently on jruby in Evergreen
describe Mongo::Auth::SCRAM::Conversation, retry: 3 do

  let(:conversation) do
    described_class.new(user, mechanism)
  end

  context 'when SCRAM-SHA-1 is used', if: scram_sha_1_enabled? do

    let(:user) do
      Mongo::Auth::User.new(
        database: Mongo::Database::ADMIN,
        user: 'user',
        password: 'pencil',
        # We specify SCRAM-SHA-1 so that we don't accidentally use SCRAM-SHA-256 on newer server versions.
        auth_mech: 'SCRAM-SHA-1'
      )
    end

    let(:mechanism) do
      'SCRAM-SHA-1'
    end

    describe '#start' do

      let(:query) do
        conversation.start
      end

      before do
        expect(SecureRandom).to receive(:base64).once.and_return('NDA2NzU3MDY3MDYwMTgy')
      end

      let(:selector) do
        query.selector
      end

      it 'sets the sasl start flag' do
        expect(selector[:saslStart]).to eq(1)
      end

      it 'sets the auto authorize flag' do
        expect(selector[:autoAuthorize]).to eq(1)
      end

      it 'sets the mechanism' do
        expect(selector[:mechanism]).to eq('SCRAM-SHA-1')
      end

      it 'sets the payload' do
        expect(selector[:payload].data).to eq('n,,n=user,r=NDA2NzU3MDY3MDYwMTgy')
      end
    end

    describe '#continue' do
      fails_on_jruby

      let(:reply) do
        Mongo::Protocol::Message.new
      end

      let(:documents) do
        [{
          'conversationId' => 1,
          'done' => false,
          'payload' => payload,
          'ok' => 1.0
        }]
      end

      before do
        expect(SecureRandom).to receive(:base64).once.and_return('NDA2NzU3MDY3MDYwMTgy')
        allow(reply).to receive(:documents).and_return(documents)
      end

      context 'when the server rnonce starts with the nonce' do

        let(:payload) do
          BSON::Binary.new(
            'r=NDA2NzU3MDY3MDYwMTgyt7/+IWaw1HaZZ5NmPJUTWapLpH2Gg+d8,s=AVvQXzAbxweH2RYDICaplw==,i=10000'
          )
        end

        let(:query) do
          conversation.continue(reply)
        end

        let(:selector) do
          query.selector
        end

        it 'sets the conversation id' do
          expect(selector[:conversationId]).to eq(1)
        end

        it 'sets the payload' do
          expect(selector[:payload].data).to eq(
            'c=biws,r=NDA2NzU3MDY3MDYwMTgyt7/+IWaw1HaZZ5NmPJUTWapLpH2Gg+d8,p=qYUYNy6SQ9Jucq9rFA9nVgXQdbM='
          )
        end

        it 'sets the continue flag' do
          expect(selector[:saslContinue]).to eq(1)
        end
      end

      context 'when the server nonce does not start with the nonce' do

        let(:payload) do
          BSON::Binary.new(
            'r=NDA2NzU4MDY3MDYwMTgyt7/+IWaw1HaZZ5NmPJUTWapLpH2Gg+d8,s=AVvQXzAbxweH2RYDICaplw==,i=10000'
          )
        end

        it 'raises an error' do
          expect {
            conversation.continue(reply)
          }.to raise_error(Mongo::Error::InvalidNonce)
        end
      end
    end

    describe '#finalize' do
      fails_on_jruby

      let(:continue_reply) do
        Mongo::Protocol::Message.new
      end

      let(:continue_documents) do
        [{
          'conversationId' => 1,
          'done' => false,
          'payload' => continue_payload,
          'ok' => 1.0
        }]
      end

      let(:continue_payload) do
        BSON::Binary.new(
          'r=NDA2NzU3MDY3MDYwMTgyt7/+IWaw1HaZZ5NmPJUTWapLpH2Gg+d8,s=AVvQXzAbxweH2RYDICaplw==,i=10000'
        )
      end

      let(:reply) do
        Mongo::Protocol::Message.new
      end

      let(:documents) do
        [{
          'conversationId' => 1,
          'done' => false,
          'payload' => payload,
          'ok' => 1.0
        }]
      end

      before do
        expect(SecureRandom).to receive(:base64).once.and_return('NDA2NzU3MDY3MDYwMTgy')
        allow(continue_reply).to receive(:documents).and_return(continue_documents)
        allow(reply).to receive(:documents).and_return(documents)
      end

      context 'when the verifier matches the server signature' do

        let(:payload) do
          BSON::Binary.new('v=gwo9E8+uifshm7ixj441GvIfuUY=')
        end

        let(:query) do
          conversation.continue(continue_reply)
          conversation.finalize(reply)
        end

        let(:selector) do
          query.selector
        end

        it 'sets the conversation id' do
          expect(selector[:conversationId]).to eq(1)
        end

        it 'sets the empty payload' do
          expect(selector[:payload].data).to eq('')
        end

        it 'sets the continue flag' do
          expect(selector[:saslContinue]).to eq(1)
        end
      end

      context 'when the verifier does not match the server signature' do

        let(:payload) do
          BSON::Binary.new('v=LQ+8yhQeVL2a3Dh+TDJ7xHz4Srk=')
        end

        it 'raises an error' do
          expect {
            conversation.continue(continue_reply)
            conversation.finalize(reply)
          }.to raise_error(Mongo::Error::InvalidSignature)
        end
      end
    end
  end

  context 'when SCRAM-SHA-256 is used' do
    require_scram_sha_256_support

    let(:user) do
      Mongo::Auth::User.new(
        database: Mongo::Database::ADMIN,
        user: 'user',
        password: 'pencil',
        auth_mech: 'SCRAM-SHA-256'
      )
    end

    let(:mechanism) do
      'SCRAM-SHA-256'
    end

    describe '#start' do

      let(:query) do
        conversation.start
      end

      before do
        expect(SecureRandom).to receive(:base64).once.and_return('rOprNGfwEbeRWgbNEkqO')
      end

      let(:selector) do
        query.selector
      end

      it 'sets the sasl start flag' do
        expect(selector[:saslStart]).to eq(1)
      end

      it 'sets the auto authorize flag' do
        expect(selector[:autoAuthorize]).to eq(1)
      end

      it 'sets the mechanism' do
        expect(selector[:mechanism]).to eq('SCRAM-SHA-256')
      end

      it 'sets the payload' do
        expect(selector[:payload].data).to eq('n,,n=user,r=rOprNGfwEbeRWgbNEkqO')
      end
    end

    describe '#continue' do

      let(:reply) do
        Mongo::Protocol::Message.new
      end

      let(:documents) do
        [{
          'conversationId' => 1,
          'done' => false,
          'payload' => payload,
          'ok' => 1.0
        }]
      end

      before do
        expect(SecureRandom).to receive(:base64).once.and_return('rOprNGfwEbeRWgbNEkqO')
        allow(reply).to receive(:documents).and_return(documents)
      end

      context 'when the server rnonce starts with the nonce' do

        let(:payload) do
          BSON::Binary.new(
            'r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096'
          )
        end

        let(:query) do
          conversation.continue(reply)
        end

        let(:selector) do
          query.selector
        end

        it 'sets the conversation id' do
          expect(selector[:conversationId]).to eq(1)
        end

        it 'sets the payload' do
          expect(selector[:payload].data).to eq(
            'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ='
          )
        end

        it 'sets the continue flag' do
          expect(selector[:saslContinue]).to eq(1)
        end
      end

      context 'when the server nonce does not start with the nonce' do

        let(:payload) do
          BSON::Binary.new(
            'r=sOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096'
          )
        end

        it 'raises an error' do
          expect {
            conversation.continue(reply)
          }.to raise_error(Mongo::Error::InvalidNonce)
        end
      end
    end

    describe '#finalize' do

      let(:continue_reply) do
        Mongo::Protocol::Message.new
      end

      let(:continue_documents) do
        [{
          'conversationId' => 1,
          'done' => false,
          'payload' => continue_payload,
          'ok' => 1.0
        }]
      end

      let(:continue_payload) do
        BSON::Binary.new(
          'r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096'
        )
      end

      let(:reply) do
        Mongo::Protocol::Message.new
      end

      let(:documents) do
        [{
          'conversationId' => 1,
          'done' => false,
          'payload' => payload,
          'ok' => 1.0
        }]
      end

      before do
        expect(SecureRandom).to receive(:base64).once.and_return('rOprNGfwEbeRWgbNEkqO')
        allow(continue_reply).to receive(:documents).and_return(continue_documents)
        allow(reply).to receive(:documents).and_return(documents)
      end

      context 'when the verifier matches the server signature' do

        let(:payload) do
          BSON::Binary.new(' v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=')
        end

        let(:query) do
          conversation.continue(continue_reply)
          conversation.finalize(reply)
        end

        let(:selector) do
          query.selector
        end

        it 'sets the conversation id' do
          expect(selector[:conversationId]).to eq(1)
        end

        it 'sets the empty payload' do
          expect(selector[:payload].data).to eq('')
        end

        it 'sets the continue flag' do
          expect(selector[:saslContinue]).to eq(1)
        end
      end

      context 'when the verifier does not match the server signature' do

        let(:payload) do
          BSON::Binary.new('v=7rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4=')
        end

        it 'raises an error' do
          expect {
            conversation.continue(continue_reply)
            conversation.finalize(reply)
          }.to raise_error(Mongo::Error::InvalidSignature)
        end
      end
    end
  end
end
