require 'spec_helper' require 'yaml' describe SoarAuthenticationToken::JwtTokenValidator do subject { SoarAuthenticationToken::JwtTokenValidator } before :all do @test_store = AuthTokenStoreProvider::StubClient.new keypair_generator = SoarAuthenticationToken::KeypairGenerator.new @valid_private_key, @valid_public_key = keypair_generator.generate @invalid_private_key, @invalid_public_key = keypair_generator.generate @test_identifier = 'a@b.co.za' @local_valid_generator_configuration = { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @valid_private_key } @local_invalid_generator_configuration = { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @invalid_private_key } @local_validator_configuration_single_key = { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'keyA' => { 'public_key' => @valid_public_key } } } @private_key_order_1, @public_key_order_1 = keypair_generator.generate @private_key_order_2, @public_key_order_2 = keypair_generator.generate @private_key_order_3, @public_key_order_3 = keypair_generator.generate @multiple_key_configuration = { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'KEYPAIR_20160107T230001' => { #Specifically add this key name out of alphabetical order 'public_key' => @public_key_order_3 }, 'KEYPAIR_20160108T200001' => { 'public_key' => @public_key_order_1 }, 'KEYPAIR_20160108T190001' => { 'public_key' => @public_key_order_2 } } } @remote_generator_configuration = { 'provider' => 'SoarAuthenticationToken::RemoteTokenGenerator', 'generator-url' => 'http://authentication-token-generator-service:9393/generate', 'generator-client-auth-token' => 'test_ecosystem_token_for_auth_token_aaapi_authenticator_service' } @remote_validator_configuration = { 'provider' => 'SoarAuthenticationToken::RemoteTokenValidator', 'validator-url' => 'http://authentication-token-validator-service:9393/validate', 'generator-client-auth-token' => 'test_ecosystem_token_for_auth_token_aaapi_authenticator_service' } @local_valid_generator = SoarAuthenticationToken::TokenGenerator.new(@local_valid_generator_configuration) @local_valid_generator.inject_store_provider(@test_store) @local_invalid_generator = SoarAuthenticationToken::TokenGenerator.new(@local_invalid_generator_configuration) @local_invalid_generator.inject_store_provider(@test_store) @remote_generator = SoarAuthenticationToken::TokenGenerator.new(@remote_generator_configuration) end it 'has a version number' do expect(SoarAuthenticationToken::VERSION).not_to be nil end describe "#validate" do context "given single key" do let!(:iut) { iut = SoarAuthenticationToken::TokenValidator.new(@local_validator_configuration_single_key) iut.inject_store_provider(@test_store) iut } context 'given valid token' do let!(:token_validation_result) { token, token_generator_meta = @local_valid_generator.generate(authenticated_identifier: @test_identifier) iut.validate(authentication_token: token) } let!(:token_validity) { token_validation_result[0] } let!(:token_meta) { token_validation_result[1] } let!(:message) { token_validation_result[2] } it 'indicate token is valid' do expect(token_validity).to eq true end it 'provide the token meta' do expect(token_meta['authenticated_identifier']).to eq @test_identifier end it 'provide a message indicating that the token is valid' do expect(message).to match /Valid token for/ end end context 'given expired token' do let!(:token_validation_result) { token, token_generator_meta = @local_valid_generator.generate(authenticated_identifier: @test_identifier) allow(Time).to receive(:now).and_return(Time.now+one_year_in_seconds) iut.validate(authentication_token: token) } let!(:token_validity) { token_validation_result[0] } let!(:token_meta) { token_validation_result[1] } let!(:message) { token_validation_result[2] } it 'indicate token is invalid' do expect(token_validity).to eq false end it 'does not provide the token meta' do expect(token_meta).to eq nil end it 'provide a message indicating that the token is invalid' do expect(message).to match /Expired token/ end end context 'given unknown token (not in store)' do let!(:token_validation_result) { token, token_generator_meta = @local_valid_generator.generate(authenticated_identifier: @test_identifier) @test_store.instance_variable_set("@store", []) #clear store iut.validate(authentication_token: token) } let!(:token_validity) { token_validation_result[0] } let!(:token_meta) { token_validation_result[1] } let!(:message) { token_validation_result[2] } it 'indicate that token is invalid' do expect(token_validity).to eq false end it 'does not provide the token meta' do expect(token_meta).to eq nil end it 'provide a message indicating that the token is invalid' do expect(message).to match /Unknown token/ end end context 'given invalid token (garbage or different key)' do let!(:token_validation_result) { token, token_generator_meta = @remote_generator.generate(authenticated_identifier: @test_identifier) iut.validate(authentication_token: token) } let!(:token_validity) { token_validation_result[0] } let!(:token_meta) { token_validation_result[1] } let!(:message) { token_validation_result[2] } it 'indicate token is invalid' do expect(token_validity).to eq false end it 'does not provide the token meta' do expect(token_meta).to eq nil end it 'provide a message indicating that the token is invalid' do expect(message).to match /Token decode\/verification failure/ end end end context 'given multiple keys' do let!(:iut) { iut = SoarAuthenticationToken::TokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) iut } context 'given a token that cannot be verified with any of the public keys' do let!(:token) { generator_configuration = { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @invalid_private_key } generator = SoarAuthenticationToken::TokenGenerator.new(generator_configuration) generator.inject_store_provider(@test_store) token, token_generator_meta = generator.generate(authenticated_identifier: @test_identifier) token } let!(:token_validation_result) { iut.validate(authentication_token: token) } let!(:token_validity) { token_validation_result[0] } let!(:token_meta) { token_validation_result[1] } let!(:message) { token_validation_result[2] } it 'responds indicating the token is invalid' do expect(token_validity).to eq false end it 'attempts to use the public keys in alphabetical order' do iut = SoarAuthenticationToken::JwtTokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) expect(iut).to receive(:attempt_decode_using_a_key).once.ordered.with(token, @multiple_key_configuration['keys']['KEYPAIR_20160108T200001']).and_return(nil) expect(iut).to receive(:attempt_decode_using_a_key).once.ordered.with(token, @multiple_key_configuration['keys']['KEYPAIR_20160108T190001']).and_return(nil) expect(iut).to receive(:attempt_decode_using_a_key).once.ordered.with(token, @multiple_key_configuration['keys']['KEYPAIR_20160107T230001']).and_return(nil) iut.validate(authentication_token: token) end it 'attempts to use all the public keys' do iut = SoarAuthenticationToken::JwtTokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) expect(iut).to receive(:attempt_decode_using_a_key).exactly(3).times iut.validate(authentication_token: token) end end context 'given a token that can be verified with the first/latest public key in the list' do let!(:token) { generator_configuration = { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_order_1 } generator = SoarAuthenticationToken::TokenGenerator.new(generator_configuration) generator.inject_store_provider(@test_store) token, token_generator_meta = generator.generate(authenticated_identifier: @test_identifier) token } it 'responds indicating the token is valid' do iut = SoarAuthenticationToken::JwtTokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) token_validity, token_meta, message = iut.validate(authentication_token: token) expect(token_validity).to eq true end it 'attempts to use the first public key (and only the first key)' do #Do a bit of work to get fake token meta iut = SoarAuthenticationToken::TokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) validity, token_meta, messages = iut.validate(authentication_token: token) #Now create iut again for test iut = SoarAuthenticationToken::JwtTokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) expect(iut).to receive(:attempt_decode_using_a_key).once.with(token, @multiple_key_configuration['keys']['KEYPAIR_20160108T200001']).and_return([token_meta]) iut.validate(authentication_token: token) end end context 'given a token that can be verified with the second public key in the list' do let!(:token) { generator_configuration = { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_order_2 } generator = SoarAuthenticationToken::TokenGenerator.new(generator_configuration) generator.inject_store_provider(@test_store) token, token_generator_meta = generator.generate(authenticated_identifier: @test_identifier) token } let!(:token_validation_result) { iut.validate(authentication_token: token) } let!(:token_validity) { token_validation_result[0] } let!(:token_meta) { token_validation_result[1] } let!(:message) { token_validation_result[2] } it 'responds indicating the token is valid' do expect(token_validity).to eq true end it 'attempts to use public keys in order (first the first key, second the second key, not the third key)' do #Do a bit of work to get fake token meta iut = SoarAuthenticationToken::TokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) validity, token_meta, messages = iut.validate(authentication_token: token) #Now create iut again for test iut = SoarAuthenticationToken::JwtTokenValidator.new(@multiple_key_configuration) iut.inject_store_provider(@test_store) expect(iut).to receive(:attempt_decode_using_a_key).once.with(token, @multiple_key_configuration['keys']['KEYPAIR_20160108T200001']).and_return(nil) expect(iut).to receive(:attempt_decode_using_a_key).once.with(token, @multiple_key_configuration['keys']['KEYPAIR_20160108T190001']).and_return([token_meta]) expect(iut).not_to receive(:attempt_decode_using_a_key).with(token, @multiple_key_configuration['keys']['KEYPAIR_20160107T230001']) iut.validate(authentication_token: token) end end end end end