require 'spec_helper' describe SoarAuthenticationToken::ConfigRotator do subject { SoarAuthenticationToken::ConfigRotator.new } before :all do keypair_generator = SoarAuthenticationToken::KeypairGenerator.new @private_key_1, @public_key_1 = keypair_generator.generate @private_key_2, @public_key_2 = keypair_generator.generate @private_key_3, @public_key_3 = keypair_generator.generate @private_key_4, @public_key_4 = keypair_generator.generate @valid_validator_config = { 'auth_token_validator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 }, 'KEYPAIR_20160107T230201' => { 'public_key' => @public_key_2 }, 'KEYPAIR_20160107T230301' => { 'public_key' => @public_key_3 } } } } @valid_generator_config = { 'auth_token_generator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_3, 'key_description' => 'original key' } } end it 'has a version number' do expect(SoarAuthenticationToken::VERSION).not_to be nil end context "when trimming public keys in the validator configuration to one less than maximum allowed" do context "with key list containing no keys" do let!(:validator_configuration) { { 'auth_token_validator' => { 'keys' => { } } } } it 'the resulting list is kept intact with no keys' do test_configuration = validator_configuration.dup subject.send(:trim_public_keys, test_configuration) expect(test_configuration).to eq( { 'auth_token_validator' => { 'keys' => { } } }) end end context "with key list containing lower than maximum allowed number of keys" do let!(:validator_configuration) { { 'auth_token_validator' => { 'keys' => { 'KEYPAIR_20160107T230001' => [], 'KEYPAIR_20160107T230101' => [] } } } } it 'the resulting list is kept intact' do test_configuration = validator_configuration.dup subject.send(:trim_public_keys, test_configuration) expect(test_configuration).to eq( { 'auth_token_validator' => { 'keys' => { 'KEYPAIR_20160107T230001' => [], 'KEYPAIR_20160107T230101' => [] } } }) end end context "with key list containing the maximum allowed number of keys" do let!(:validator_configuration) { { 'auth_token_validator' => { 'keys' => { 'KEYPAIR_20160107T230001' => [], 'KEYPAIR_20160107T230101' => [], 'KEYPAIR_20160107T230201' => [] } } } } it 'the key named with the lowest string value (ordered alphabetically) is removed' do test_configuration = validator_configuration.dup subject.send(:trim_public_keys, test_configuration) expect(test_configuration).to eq( { 'auth_token_validator' => { 'keys' => { 'KEYPAIR_20160107T230101' => [], 'KEYPAIR_20160107T230201' => [] } } }) end end context "with key list containing more than the maximum allowed number of keys" do let!(:validator_configuration) { { 'auth_token_validator' => { 'keys' => { 'KEYPAIR_20160107T230001' => [], 'KEYPAIR_20160107T230401' => [], 'KEYPAIR_20160107T230201' => [], 'KEYPAIR_20160107T230101' => [] } } } } it 'the keys named with the lowest string values (ordered alphabetically) is removed until the it contains one less than the maximum allowed' do test_configuration = validator_configuration.dup subject.send(:trim_public_keys, test_configuration) expect(test_configuration).to eq( { 'auth_token_validator' => { 'keys' => { 'KEYPAIR_20160107T230201' => [], 'KEYPAIR_20160107T230401' => [] } } }) end end end context "when rotating key pairs" do context "with invalid generator configuration in a json formated file" do let!(:validator_config_file_name) { filename = "validator_config.json" File.open(filename,"w") do |f| f.write(@valid_validator_config.to_json) end filename } let!(:generator_config_file_name) { filename = "generator_config.json" File.open(filename,"w") do |f| f.write({'auth_token_generator' => {}}.to_json) #Empty hash is an Invalid hash end filename } it 'raises an error indicating the generator configuration is invalid' do expect { subject.rotate_json_config_files(generator_file_name: generator_config_file_name, validator_file_name: validator_config_file_name) }.to raise_error(ArgumentError, /private_key not in config/) end end context "with invalid validator configuration in a json formated file" do let!(:validator_config_file_name) { filename = "validator_config.json" File.open(filename,"w") do |f| f.write({'auth_token_validator' => {}}.to_json) #Empty hash is an invalid hash end filename } let!(:generator_config_file_name) { filename = "generator_config.json" File.open(filename,"w") do |f| f.write(@valid_generator_config.to_json) end filename } it 'raises an error indicating the validator configuration is invalid' do expect { subject.rotate_json_config_files(generator_file_name: generator_config_file_name, validator_file_name: validator_config_file_name) }.to raise_error(ArgumentError, /keys not in config/) end end context "with valid generator/validator configurations in a json formated file" do let!(:validator_config_file_name) { filename = "validator_config.json" File.open(filename,"w") do |f| f.write(@valid_validator_config.to_json) end filename } let!(:generator_config_file_name) { filename = "generator_config.json" File.open(filename,"w") do |f| f.write(@valid_generator_config.to_json) end filename } it 'replaces the newly generated private key to the generator configuration' do subject.rotate_json_config_files(generator_file_name: generator_config_file_name, validator_file_name: validator_config_file_name) generator_config = JSON.parse(File.read(generator_config_file_name)) expect(generator_config['auth_token_generator']['private_key']).to_not eq(@valid_generator_config['auth_token_generator']['private_key']) end it 'adds the newly generated public key to the validator configuration' do subject.rotate_json_config_files(generator_file_name: generator_config_file_name, validator_file_name: validator_config_file_name) generator_config = JSON.parse(File.read(generator_config_file_name)) validator_config = JSON.parse(File.read(validator_config_file_name)) expect(validator_config['auth_token_validator']['keys'][generator_config['auth_token_generator']['key_description']]).to_not be nil end it 'removes the oldest public key from the validator configuration in keeping with maximum number of keys' do subject.rotate_json_config_files(generator_file_name: generator_config_file_name, validator_file_name: validator_config_file_name) generator_config = JSON.parse(File.read(generator_config_file_name)) validator_config = JSON.parse(File.read(validator_config_file_name)) expect(validator_config['auth_token_validator']['keys'].size).to be 3 end end end context "when confirming that configurations are valid" do context "given validator (with single public key) configuration that includes generator key" do let!(:validator_config) {{ 'auth_token_validator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 } } } }} let!(:generator_config) {{ 'auth_token_generator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_1, 'key_description' => 'original key' } }} it 'responds that the configuration combination is valid' do valid = subject.configurations_match_and_valid?(generator_config: generator_config, validator_config: validator_config) expect(valid).to be true end end context "given validator (with single public key) configuration that does not include generator key" do let!(:validator_config) {{ 'auth_token_validator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 } } } }} let!(:generator_config) {{ 'auth_token_generator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_2, 'key_description' => 'original key' } }} it 'responds that the configuration combination is not valid' do valid = subject.configurations_match_and_valid?(generator_config: generator_config, validator_config: validator_config) expect(valid).to be false end end context "given validator (with multiple public keys) configuration that include generator key" do let!(:validator_config) {{ 'auth_token_validator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 }, 'KEYPAIR_20160107T230201' => { 'public_key' => @public_key_2 }, 'KEYPAIR_20160107T230301' => { 'public_key' => @public_key_3 }, } } }} let!(:generator_config) {{ 'auth_token_generator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_2, 'key_description' => 'original key' } }} it 'responds that the configuration combination is valid' do valid = subject.configurations_match_and_valid?(generator_config: generator_config, validator_config: validator_config) expect(valid).to be true end end context "given validator (with multiple public keys) configuration that does not include generator key" do let!(:validator_config) {{ 'auth_token_validator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenValidator', 'keys' => { 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 }, 'KEYPAIR_20160107T230201' => { 'public_key' => @public_key_2 }, 'KEYPAIR_20160107T230301' => { 'public_key' => @public_key_3 }, } } }} let!(:generator_config) {{ 'auth_token_generator' => { 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator', 'private_key' => @private_key_4, 'key_description' => 'original key' } }} it 'responds that the configuration combination is not valid' do valid = subject.configurations_match_and_valid?(generator_config: generator_config, validator_config: validator_config) expect(valid).to be false end end end end