soar_authentication_token 4.0.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7a8624b1f055f6acb2b0389a91287c6da5e28226
4
- data.tar.gz: a5a075c86334c09962bd1fa319ca735cc788722c
3
+ metadata.gz: a52f85114800057ab0311580dcaa924d9abb73cb
4
+ data.tar.gz: 147f69366980200dc8e695621a1884099ab3b3dd
5
5
  SHA512:
6
- metadata.gz: 42becbe6549bf863769195ccb7e131d4f199573f41a436b5c504fc5f4b900a6a618e4544e59b2960f760583ab16309fae323738a5cb4e174a15a72ce9a249536
7
- data.tar.gz: ae95a54d5d15d4c71bb7fd6455d1c1c58014b80296383520685f3dcbd3531d097c5e2cd0bc386443893588f323182affee4bcc170cc44561bb01f5928eb270fd
6
+ metadata.gz: 05a4b09349427a6dc587f0ec6c968ef3b25fbed863e801c5148f3ff98499dfb1950e01f17905778dfbf05641cc5361e3fb17b4efcb47915a5bcda6e3f2129699
7
+ data.tar.gz: 2d436a9426bbb03364391785061c30742c8d62b3283895629a2269017a1c37adfe5132f72f485bad54870f4ef332de9744d2b56a19067f8e1918a0546a4e2d4f
data/.gitignore CHANGED
@@ -2,3 +2,5 @@ Gemfile.lock
2
2
  *.gem
3
3
  .byebug_history
4
4
  coverage
5
+ validator_config.json
6
+ generator_config.json
data/README.md CHANGED
@@ -43,9 +43,9 @@ In this mode the StaticTokenValidator is configured with a list of preconfigured
43
43
  Run the rspec test tests using docker compose:
44
44
 
45
45
  ```bash
46
- docker-compose build --force-rm --no-cache
46
+ export UID; docker-compose build --force-rm --no-cache
47
47
  docker-compose down
48
- docker-compose run --rm soar-authentication-token
48
+ export UID; docker-compose run --rm soar-authentication-token
49
49
  docker-compose down
50
50
  ```
51
51
 
@@ -56,7 +56,7 @@ For test purposes this repo relies on various other repos with services. This i
56
56
  ```bash
57
57
  git pull && git submodule foreach 'git fetch origin --tags; git checkout master; git pull'
58
58
  docker-compose down
59
- docker-compose build
59
+ docker-compose build --force-rm --no-cache
60
60
  ```
61
61
 
62
62
  ## Usage
@@ -78,7 +78,7 @@ First step is to configure the middleware. Actually you are not configuring the
78
78
 
79
79
  ```ruby
80
80
  @iut_configuration = {
81
- 'provider' => 'RemoteTokenValidator',
81
+ 'provider' => 'SoarAuthenticationToken::RemoteTokenValidator',
82
82
  'validator-url' => 'http://authentication-token-validator-service:9393/validate'
83
83
  }
84
84
  @iut = SoarAuthenticationToken::RackMiddleware.new(@test_app, @iut_configuration)
@@ -95,7 +95,7 @@ The class generates tokens or requests a token from a generator service as confi
95
95
  For remote token generation, configure as such:
96
96
  ```ruby
97
97
  @configuration_remote = {
98
- 'provider' => 'RemoteTokenGenerator',
98
+ 'provider' => 'SoarAuthenticationToken::RemoteTokenGenerator',
99
99
  'generator-url' => 'http://authentication-token-generator-service:9393/generate',
100
100
  'generator-client-auth-token' => 'xxxx'
101
101
  }
@@ -106,7 +106,7 @@ For local token generation, configure as such:
106
106
  generator = SoarAuthenticationToken::KeypairGenerator.new
107
107
  private_key, public_key = generator.generate
108
108
  @configuration_local = {
109
- 'provider' => 'JwtTokenGenerator',
109
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
110
110
  'private_key' => private_key
111
111
  }
112
112
  ```
@@ -132,7 +132,7 @@ The class validates tokens or requests validation of a token from a validation s
132
132
  For remote token validation, configure as such:
133
133
  ```ruby
134
134
  @configuration_remote = {
135
- 'provider' => 'RemoteTokenGenerator',
135
+ 'provider' => 'SoarAuthenticationToken::RemoteTokenGenerator',
136
136
  'validator-url' => 'http://authentication-token-validator-service:9393/validate',
137
137
  }
138
138
  ```
@@ -142,7 +142,7 @@ For local token validation, configure as such:
142
142
  generator = SoarAuthenticationToken::KeypairGenerator.new
143
143
  private_key, public_key = generator.generate
144
144
  @configuration_local = {
145
- 'provider' => 'JwtTokenValidator',
145
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
146
146
  'public_key' => public_key
147
147
  }
148
148
  ```
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ require "pathname"
3
+ bin_file = Pathname.new(__FILE__).realpath
4
+ $:.unshift File.expand_path("../../lib", bin_file)
5
+
6
+ require 'soar_authentication_token'
7
+ require 'thor'
8
+
9
+ class RotateConfigCLI < Thor
10
+ desc "rotate [OPTIONS]", "rotate configurations"
11
+ option :generator_config_file, :aliases => '-g', :desc => 'Configuration file of the generator'
12
+ option :validator_config_file, :aliases => '-v', :desc => 'Configuration file of the validator'
13
+ def rotate
14
+ raise 'generator_config_file must be specified' unless options['generator_config_file']
15
+
16
+ rotator = SoarAuthenticationToken::ConfigRotator.new
17
+ rotator.rotate_json_config_files(generator_file_name: options['generator_config_file'],
18
+ validator_file_name: options['validator_config_file'])
19
+ end
20
+ default_task :rotate
21
+ end
22
+
23
+ RotateConfigCLI.start(ARGV)
data/docker-compose.yml CHANGED
@@ -1,7 +1,8 @@
1
1
  version: '2.0'
2
2
  services:
3
3
  soar-authentication-token:
4
- command: /bin/bash -c 'sleep 5; bundle exec rspec -cfd spec'
4
+ command: /bin/bash -c 'bundle exec rspec -cfd ./spec/'
5
+ user: $UID:$UID
5
6
  build: .
6
7
  image: soar-authentication-token
7
8
  volumes:
@@ -7,6 +7,7 @@ require 'soar_authentication_token/providers/remote_token_generator'
7
7
  require 'soar_authentication_token/providers/remote_token_validator'
8
8
  require 'soar_authentication_token/providers/static_token_validator'
9
9
  require 'soar_authentication_token/keypair_generator'
10
+ require 'soar_authentication_token/config_rotator'
10
11
  require 'soar_authentication_token/token_generator'
11
12
  require 'soar_authentication_token/token_validator'
12
13
  require 'soar_authentication_token/rack_middleware'
@@ -0,0 +1,119 @@
1
+ require 'json'
2
+
3
+ module SoarAuthenticationToken
4
+ class ConfigRotator
5
+ attr_accessor :maximum_number_of_public_keys
6
+
7
+ def initialize
8
+ @maximum_number_of_public_keys = 3
9
+ end
10
+
11
+ def rotate_json_config_files(generator_file_name:, validator_file_name:)
12
+ generator_config = JSON.parse(File.read(generator_file_name))
13
+ validator_config = JSON.parse(File.read(validator_file_name))
14
+ generator_config, validator_config = rotate_configs(generator_config: generator_config,
15
+ validator_config: validator_config)
16
+ File.open(generator_file_name,"w") do |f|
17
+ f.write(JSON.pretty_generate generator_config)
18
+ f.close
19
+ end
20
+ File.open(validator_file_name,"w") do |f|
21
+ f.write(JSON.pretty_generate validator_config)
22
+ f.close
23
+ end
24
+ end
25
+
26
+ def rotate_configs(generator_config:, validator_config:)
27
+ private_key, public_key = KeypairGenerator.new.generate
28
+ key_description = generate_keypair_description
29
+ updated_generator_config = rotate_generator_config(config: generator_config,new_private_key: private_key,new_key_description: key_description)
30
+ updated_validator_config = rotate_validator_config(config: validator_config,new_public_key: public_key, new_key_description: key_description)
31
+ raise 'generated configuration does not match' unless configurations_match_and_valid?(generator_config: updated_generator_config,validator_config: updated_validator_config)
32
+ [updated_generator_config, updated_validator_config]
33
+ end
34
+
35
+ def rotate_generator_config(config: ,new_private_key: ,new_key_description:)
36
+ validate_generator_config(config)
37
+ new_config = config.dup
38
+ new_config['private_key'] = new_private_key
39
+ new_config['key_description'] = new_key_description
40
+ new_config
41
+ end
42
+
43
+ def rotate_validator_config(config: ,new_public_key: ,new_key_description:)
44
+ validate_validator_config(config)
45
+ new_config = config.dup
46
+ trim_public_keys(new_config)
47
+ new_config['keys'][new_key_description] = { 'public_key' => new_public_key }
48
+ new_config
49
+ end
50
+
51
+ def configurations_match_and_valid?(generator_config:,validator_config:)
52
+ validate_generator_config(generator_config)
53
+ validate_validator_config(validator_config)
54
+ test_token = generate_test_token(generator_config)
55
+ validate_test_token(validator_config,test_token)
56
+ end
57
+
58
+ private
59
+
60
+ def trim_public_keys(config)
61
+ traversed_keys = 0
62
+ config['keys'].sort.reverse_each do |key_name, key_data|
63
+ traversed_keys += 1
64
+ config['keys'].delete(key_name) if traversed_keys >= @maximum_number_of_public_keys
65
+ end
66
+ end
67
+
68
+ def generate_keypair_description
69
+ "KEYPAIR_#{Time.now.strftime("%Y%m%dT%H%M%S")}"
70
+ end
71
+
72
+ def generate_test_token(generator_config)
73
+ generator = SoarAuthenticationToken::TokenGenerator.new(generator_config)
74
+ generator.inject_store_provider(DummyStorageClient.new)
75
+ token, meta = generator.generate(authenticated_identifier: 'test')
76
+ token
77
+ end
78
+
79
+ def validate_test_token(validator_config,test_token)
80
+ validator = SoarAuthenticationToken::TokenValidator.new(validator_config)
81
+ validator.inject_store_provider(DummyStorageClient.new)
82
+ validity, meta, message = validator.validate(authentication_token: test_token)
83
+ validity
84
+ end
85
+
86
+ def validate_generator_config(config)
87
+ raise ArgumentError, 'private_key not in config' unless config['private_key']
88
+ end
89
+
90
+ def validate_validator_config(config)
91
+ raise ArgumentError, 'keys not in config' unless config['keys']
92
+ end
93
+ end
94
+
95
+
96
+ class DummyStorageClient
97
+ def initialize(configuration = {})
98
+ end
99
+ def store_failure(state = true)
100
+ end
101
+ def store_failure?
102
+ false
103
+ end
104
+ def add(token_identifier:, authenticated_identifier:, token_issue_time:, token_expiry_time:, flow_identifier: nil)
105
+ end
106
+ def remove(token_identifier:, flow_identifier: nil)
107
+ true
108
+ end
109
+ def token_exist?(token_identifier:, authenticated_identifier:, token_issue_time:, token_expiry_time:, flow_identifier: nil)
110
+ true
111
+ end
112
+ def remove_tokens_for(authenticated_identifier:, flow_identifier: nil)
113
+ true
114
+ end
115
+ def list_tokens_for(authenticated_identifier:, flow_identifier: nil)
116
+ nil
117
+ end
118
+ end
119
+ end
@@ -2,8 +2,6 @@ require 'soar_xt'
2
2
  require 'jwt'
3
3
  require 'securerandom'
4
4
  require 'time'
5
- require 'json'
6
- require 'authenticated_client'
7
5
 
8
6
  module SoarAuthenticationToken
9
7
  class JwtTokenGenerator
@@ -13,7 +11,7 @@ module SoarAuthenticationToken
13
11
 
14
12
  def initialize(configuration)
15
13
  @configuration = merge_with_default_configuration(configuration)
16
- validate_local_mode_configuration
14
+ validate_configuration
17
15
  @private_key = OpenSSL::PKey::EC.new(@configuration['private_key'])
18
16
  end
19
17
 
@@ -43,9 +41,9 @@ module SoarAuthenticationToken
43
41
  JWT.encode(meta, @private_key, 'ES512')
44
42
  end
45
43
 
46
- def validate_local_mode_configuration
47
- raise "'private_key' must be configured in local mode" unless @configuration['private_key']
48
- raise "'expiry' must be configured in local mode" unless @configuration['expiry']
44
+ def validate_configuration
45
+ raise "'private_key' must be configured" unless @configuration['private_key']
46
+ raise "'expiry' must be configured" unless @configuration['expiry']
49
47
  raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
50
48
  end
51
49
 
@@ -1,5 +1,4 @@
1
1
  require 'jwt'
2
- require 'authenticated_client'
3
2
 
4
3
  module SoarAuthenticationToken
5
4
  class JwtTokenValidator
@@ -7,8 +6,6 @@ module SoarAuthenticationToken
7
6
  @configuration = configuration
8
7
  set_configuration_defaults
9
8
  validate_configuration
10
- @public_key = OpenSSL::PKey::EC.new(@configuration['public_key'])
11
- @public_key.private_key = nil
12
9
  end
13
10
 
14
11
  def inject_store_provider(store_provider)
@@ -16,12 +13,12 @@ module SoarAuthenticationToken
16
13
  end
17
14
 
18
15
  def validate(authentication_token:,flow_identifier: nil)
19
- meta = decode_token_meta(authentication_token)
16
+ decoded_token_payload = decode(authentication_token)
17
+ return rejection_result(reason: 'Token decode/verification failure') if decoded_token_payload.nil?
18
+ meta = compile_meta_from_payload(decoded_token_payload)
20
19
  return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
21
20
  return rejection_result(reason: "Unknown token for <#{meta['authenticated_identifier']}>") unless token_exist_in_store?(meta,flow_identifier)
22
21
  success_result(token_meta: meta)
23
- rescue JWT::VerificationError, JWT::DecodeError
24
- rejection_result(reason: 'Token decode/verification failure')
25
22
  end
26
23
 
27
24
  private
@@ -30,24 +27,13 @@ module SoarAuthenticationToken
30
27
  @configuration['expiry'] ||= 86400
31
28
  end
32
29
 
33
- def decode_token_meta(authentication_token)
34
- decoded_token_payload = decode(authentication_token)
35
- compile_meta(token_identifier: decoded_token_payload[0]['token_identifier'],
36
- authenticated_identifier: decoded_token_payload[0]['authenticated_identifier'],
37
- token_issue_time: decoded_token_payload[0]['token_issue_time'],
38
- token_expiry_time: decoded_token_payload[0]['token_expiry_time'])
39
- end
40
-
41
- def compile_meta(token_identifier:,
42
- authenticated_identifier:,
43
- token_issue_time:,
44
- token_expiry_time:)
30
+ def compile_meta_from_payload(decoded_token_payload)
45
31
  {
46
- 'token_identifier' => token_identifier,
47
- 'authenticated_identifier' => authenticated_identifier,
48
- 'token_issue_time' => token_issue_time,
49
- 'token_expiry_time' => token_expiry_time,
50
- 'token_age' => token_age(token_issue_time)
32
+ 'token_identifier' => decoded_token_payload[0]['token_identifier'],
33
+ 'authenticated_identifier' => decoded_token_payload[0]['authenticated_identifier'],
34
+ 'token_issue_time' => decoded_token_payload[0]['token_issue_time'],
35
+ 'token_expiry_time' => decoded_token_payload[0]['token_expiry_time'],
36
+ 'token_age' => token_age(decoded_token_payload[0]['token_issue_time'])
51
37
  }
52
38
  end
53
39
 
@@ -56,13 +42,25 @@ module SoarAuthenticationToken
56
42
  end
57
43
 
58
44
  def validate_configuration
59
- raise "'public_key' must be configured in local mode" unless @configuration['public_key']
60
- raise "'expiry' must be configured in local mode" unless @configuration['expiry']
45
+ raise "'keys' must be configured" unless @configuration['keys']
46
+ raise "'expiry' must be configured" unless @configuration['expiry']
61
47
  raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
62
48
  end
63
49
 
64
50
  def decode(authentication_token)
65
- JWT.decode(authentication_token, @public_key, true, { :algorithm => 'ES512' })
51
+ @configuration['keys'].sort.reverse_each do |key_name, key_data|
52
+ payload = attempt_decode_using_a_key(authentication_token,key_data)
53
+ return payload if payload
54
+ end
55
+ nil
56
+ end
57
+
58
+ def attempt_decode_using_a_key(authentication_token,key_data)
59
+ public_key = OpenSSL::PKey::EC.new(key_data['public_key'])
60
+ public_key.private_key = nil
61
+ JWT.decode(authentication_token, public_key, true, { :algorithm => 'ES512' })
62
+ rescue JWT::VerificationError, JWT::DecodeError
63
+ nil
66
64
  end
67
65
 
68
66
  def token_expired?(meta)
@@ -18,7 +18,7 @@ module SoarAuthenticationToken
18
18
  private
19
19
 
20
20
  def instantiate_provider
21
- @provider = Object::const_get("SoarAuthenticationToken::#{@configuration['provider']}").new(@configuration)
21
+ @provider = Object::const_get(@configuration['provider']).new(@configuration)
22
22
  end
23
23
 
24
24
  def validate_configuration
@@ -1,3 +1,3 @@
1
1
  module SoarAuthenticationToken
2
- VERSION = '4.0.1'
2
+ VERSION = '5.0.0'
3
3
  end
@@ -0,0 +1,283 @@
1
+ require 'spec_helper'
2
+
3
+ describe SoarAuthenticationToken::ConfigRotator do
4
+ subject { SoarAuthenticationToken::ConfigRotator.new }
5
+
6
+ before :all do
7
+ keypair_generator = SoarAuthenticationToken::KeypairGenerator.new
8
+ @private_key_1, @public_key_1 = keypair_generator.generate
9
+ @private_key_2, @public_key_2 = keypair_generator.generate
10
+ @private_key_3, @public_key_3 = keypair_generator.generate
11
+ @private_key_4, @public_key_4 = keypair_generator.generate
12
+
13
+ @valid_validator_config = {
14
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
15
+ 'keys' => {
16
+ 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 },
17
+ 'KEYPAIR_20160107T230201' => { 'public_key' => @public_key_2 },
18
+ 'KEYPAIR_20160107T230301' => { 'public_key' => @public_key_3 }
19
+ }
20
+ }
21
+ @valid_generator_config = {
22
+ 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator',
23
+ 'private_key' => @private_key_3,
24
+ 'key_description' => 'original key'
25
+ }
26
+ end
27
+
28
+ it 'has a version number' do
29
+ expect(SoarAuthenticationToken::VERSION).not_to be nil
30
+ end
31
+
32
+ context "when trimming public keys in the validator configuration to one less than maximum allowed" do
33
+
34
+ context "with key list containing no keys" do
35
+ let!(:validator_configuration) {
36
+ {
37
+ 'keys' => { }
38
+ }
39
+ }
40
+ it 'the resulting list is kept intact with no keys' do
41
+ test_configuration = validator_configuration.dup
42
+ subject.send(:trim_public_keys, test_configuration)
43
+ expect(test_configuration).to eq(
44
+ {
45
+ 'keys' => { }
46
+ })
47
+ end
48
+ end
49
+
50
+ context "with key list containing lower than maximum allowed number of keys" do
51
+ let!(:validator_configuration) {
52
+ {
53
+ 'keys' => {
54
+ 'KEYPAIR_20160107T230001' => [],
55
+ 'KEYPAIR_20160107T230101' => []
56
+ }
57
+ }
58
+ }
59
+ it 'the resulting list is kept intact' do
60
+ test_configuration = validator_configuration.dup
61
+ subject.send(:trim_public_keys, test_configuration)
62
+ expect(test_configuration).to eq( {
63
+ 'keys' => {
64
+ 'KEYPAIR_20160107T230001' => [],
65
+ 'KEYPAIR_20160107T230101' => []
66
+ }
67
+ })
68
+ end
69
+ end
70
+
71
+ context "with key list containing the maximum allowed number of keys" do
72
+ let!(:validator_configuration) {
73
+ {
74
+ 'keys' => {
75
+ 'KEYPAIR_20160107T230001' => [],
76
+ 'KEYPAIR_20160107T230101' => [],
77
+ 'KEYPAIR_20160107T230201' => []
78
+ }
79
+ }
80
+ }
81
+ it 'the key named with the lowest string value (ordered alphabetically) is removed' do
82
+ test_configuration = validator_configuration.dup
83
+ subject.send(:trim_public_keys, test_configuration)
84
+ expect(test_configuration).to eq( {
85
+ 'keys' => {
86
+ 'KEYPAIR_20160107T230101' => [],
87
+ 'KEYPAIR_20160107T230201' => []
88
+ }
89
+ })
90
+ end
91
+ end
92
+
93
+ context "with key list containing more than the maximum allowed number of keys" do
94
+ let!(:validator_configuration) {
95
+ {
96
+ 'keys' => {
97
+ 'KEYPAIR_20160107T230001' => [],
98
+ 'KEYPAIR_20160107T230401' => [],
99
+ 'KEYPAIR_20160107T230201' => [],
100
+ 'KEYPAIR_20160107T230101' => []
101
+ }
102
+ }
103
+ }
104
+ it 'the keys named with the lowest string values (ordered alphabetically) is removed until the it contains one less than the maximum allowed' do
105
+ test_configuration = validator_configuration.dup
106
+ subject.send(:trim_public_keys, test_configuration)
107
+ expect(test_configuration).to eq( {
108
+ 'keys' => {
109
+ 'KEYPAIR_20160107T230201' => [],
110
+ 'KEYPAIR_20160107T230401' => []
111
+ }
112
+ })
113
+ end
114
+ end
115
+ end
116
+
117
+ context "when rotating key pairs" do
118
+ context "with invalid generator configuration in a json formated file" do
119
+ let!(:validator_config_file_name) {
120
+ filename = "validator_config.json"
121
+ File.open(filename,"w") do |f|
122
+ f.write(@valid_validator_config.to_json)
123
+ end
124
+ filename
125
+ }
126
+ let!(:generator_config_file_name) {
127
+ filename = "generator_config.json"
128
+ File.open(filename,"w") do |f|
129
+ f.write({}.to_json) #Empty hash is an Invalid hash
130
+ end
131
+ filename
132
+ }
133
+ it 'raises an error indicating the generator configuration is invalid' do
134
+ expect { subject.rotate_json_config_files(generator_file_name: generator_config_file_name,
135
+ validator_file_name: validator_config_file_name)
136
+ }.to raise_error(ArgumentError, /private_key not in config/)
137
+ end
138
+ end
139
+
140
+ context "with invalid validator configuration in a json formated file" do
141
+ let!(:validator_config_file_name) {
142
+ filename = "validator_config.json"
143
+ File.open(filename,"w") do |f|
144
+ f.write({}.to_json) #Empty hash is an invalid hash
145
+ end
146
+ filename
147
+ }
148
+ let!(:generator_config_file_name) {
149
+ filename = "generator_config.json"
150
+ File.open(filename,"w") do |f|
151
+ f.write(@valid_generator_config.to_json)
152
+ end
153
+ filename
154
+ }
155
+ it 'raises an error indicating the validator configuration is invalid' do
156
+ expect { subject.rotate_json_config_files(generator_file_name: generator_config_file_name,
157
+ validator_file_name: validator_config_file_name)
158
+ }.to raise_error(ArgumentError, /keys not in config/)
159
+ end
160
+ end
161
+
162
+ context "with valid generator/validator configurations in a json formated file" do
163
+ let!(:validator_config_file_name) {
164
+ filename = "validator_config.json"
165
+ File.open(filename,"w") do |f|
166
+ f.write(@valid_validator_config.to_json)
167
+ end
168
+ filename
169
+ }
170
+ let!(:generator_config_file_name) {
171
+ filename = "generator_config.json"
172
+ File.open(filename,"w") do |f|
173
+ f.write(@valid_generator_config.to_json)
174
+ end
175
+ filename
176
+ }
177
+ it 'replaces the newly generated private key to the generator configuration' do
178
+ subject.rotate_json_config_files(generator_file_name: generator_config_file_name,
179
+ validator_file_name: validator_config_file_name)
180
+ generator_config = JSON.parse(File.read(generator_config_file_name))
181
+ expect(generator_config['private_key']).to_not eq(@valid_generator_config['private_key'])
182
+ end
183
+
184
+ it 'adds the newly generated public key to the validator configuration' do
185
+ subject.rotate_json_config_files(generator_file_name: generator_config_file_name,
186
+ validator_file_name: validator_config_file_name)
187
+ generator_config = JSON.parse(File.read(generator_config_file_name))
188
+ validator_config = JSON.parse(File.read(validator_config_file_name))
189
+ expect(validator_config['keys'][generator_config['key_description']]).to_not be nil
190
+ end
191
+
192
+ it 'removes the oldest public key from the validator configuration in keeping with maximum number of keys' do
193
+ subject.rotate_json_config_files(generator_file_name: generator_config_file_name,
194
+ validator_file_name: validator_config_file_name)
195
+ generator_config = JSON.parse(File.read(generator_config_file_name))
196
+ validator_config = JSON.parse(File.read(validator_config_file_name))
197
+ expect(validator_config['keys'].size).to be 3
198
+ end
199
+ end
200
+ end
201
+
202
+ context "when confirming that configurations are valid" do
203
+ context "given validator (with single public key) configuration that includes generator key" do
204
+ let!(:validator_config) {{
205
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
206
+ 'keys' => {
207
+ 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 }
208
+ }
209
+ }}
210
+ let!(:generator_config) {{
211
+ 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator',
212
+ 'private_key' => @private_key_1,
213
+ 'key_description' => 'original key'
214
+ }}
215
+ it 'responds that the configuration combination is valid' do
216
+ valid = subject.configurations_match_and_valid?(generator_config: generator_config,
217
+ validator_config: validator_config)
218
+ expect(valid).to be true
219
+ end
220
+ end
221
+
222
+ context "given validator (with single public key) configuration that does not include generator key" do
223
+ let!(:validator_config) {{
224
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
225
+ 'keys' => {
226
+ 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 }
227
+ }
228
+ }}
229
+ let!(:generator_config) {{
230
+ 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator',
231
+ 'private_key' => @private_key_2,
232
+ 'key_description' => 'original key'
233
+ }}
234
+ it 'responds that the configuration combination is not valid' do
235
+ valid = subject.configurations_match_and_valid?(generator_config: generator_config,
236
+ validator_config: validator_config)
237
+ expect(valid).to be false
238
+ end
239
+ end
240
+
241
+ context "given validator (with multiple public keys) configuration that include generator key" do
242
+ let!(:validator_config) {{
243
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
244
+ 'keys' => {
245
+ 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 },
246
+ 'KEYPAIR_20160107T230201' => { 'public_key' => @public_key_2 },
247
+ 'KEYPAIR_20160107T230301' => { 'public_key' => @public_key_3 },
248
+ }
249
+ }}
250
+ let!(:generator_config) {{
251
+ 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator',
252
+ 'private_key' => @private_key_2,
253
+ 'key_description' => 'original key'
254
+ }}
255
+ it 'responds that the configuration combination is valid' do
256
+ valid = subject.configurations_match_and_valid?(generator_config: generator_config,
257
+ validator_config: validator_config)
258
+ expect(valid).to be true
259
+ end
260
+ end
261
+
262
+ context "given validator (with multiple public keys) configuration that does not include generator key" do
263
+ let!(:validator_config) {{
264
+ 'provider' => 'SoarAuthenticationToken::JwtTokenValidator',
265
+ 'keys' => {
266
+ 'KEYPAIR_20160107T230101' => { 'public_key' => @public_key_1 },
267
+ 'KEYPAIR_20160107T230201' => { 'public_key' => @public_key_2 },
268
+ 'KEYPAIR_20160107T230301' => { 'public_key' => @public_key_3 },
269
+ }
270
+ }}
271
+ let!(:generator_config) {{
272
+ 'provider' => 'SoarAuthenticationToken::JwtTokenGenerator',
273
+ 'private_key' => @private_key_4,
274
+ 'key_description' => 'original key'
275
+ }}
276
+ it 'responds that the configuration combination is not valid' do
277
+ valid = subject.configurations_match_and_valid?(generator_config: generator_config,
278
+ validator_config: validator_config)
279
+ expect(valid).to be false
280
+ end
281
+ end
282
+ end
283
+ end