soar_authentication_token 4.0.1 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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