soar_authentication_token 2.0.3 → 3.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: baa47e9f9627e8cc1171a02147912ba35f12decc
4
- data.tar.gz: 0bb45eef7c46871eb88d372c9c17a0d22ee8aa8a
3
+ metadata.gz: c1a6ee48994978d49a24f51df0e4a0fa78e9b48c
4
+ data.tar.gz: 40f3341de578f6cff681c5478e2a90b74bc9bdbb
5
5
  SHA512:
6
- metadata.gz: 7ad0fedc417cca42974b94ad8486e2aa962d91b74d2a6e12eae5a9d7ac78035ce1b8fac04a22fae86eea08e2dd2547a0486e8cdc5101c36067397f8347fa69b0
7
- data.tar.gz: 7e6b05a978c6edef2f9cbf28af3a6c83bf1a51c0cd96687a059eafddb2e808a620db4423a123cfd9ffccadb1bac0056d342108e6cad2db3d0e44d43c6061c975
6
+ metadata.gz: e9226cada6118f03955459088df8fdf03a51601fd42b1faab5e1eaff53312331259347c7f39969c03fa9eb71b3a30b884cb89c4f4807b7d3d5e31c25040c66bc
7
+ data.tar.gz: c56869b6c2a76520e64524513913598282883715a54a0d8686bdbdc2b1ee13cae8c34969f489891b13ac3da2ec0a577274dc51d34367b48d237149a156a479c9
data/README.md CHANGED
@@ -20,6 +20,20 @@ Or install it yourself as:
20
20
 
21
21
  $ gem install soar_authentication_token
22
22
 
23
+ ## Configuration
24
+
25
+ There are three modes of operation.
26
+ ### Local
27
+ In local mode the tokens are decoded, verified and meta extracted locally using configured key material.
28
+
29
+ ### Remote
30
+ In remote mode the tokens are passed to a validation service for dynamic validation. The key material are therefore managed on the validation service. In this mode you only have to provide the url of the validation service.
31
+
32
+ ### Static
33
+ In this mode the validator are configured with a list of preconfigured static tokens. Incoming tokens are simply checked against this list. No extraction of meta is performed on the tokens but retrieved from the configuration. This mode is to be used in only two scenarios:
34
+ * Between the various authentication token services that requires authentication between themselves. These services do not have such a service to rely on. Circular dependency.
35
+ * In test scenarios where you do not want to pull in the authentication services to perform testing of your services.
36
+
23
37
 
24
38
  ## Testing
25
39
 
@@ -37,6 +51,15 @@ Locally run a subset:
37
51
  $ bundle exec rspec -cfd spec/rack_middleware_spec.rb
38
52
 
39
53
 
54
+ ## Updating
55
+
56
+ In order to pull the latest from the referenced projects, simply the following command:
57
+
58
+ ```bash
59
+ git pull && git submodule foreach 'git fetch origin --tags; git checkout master; git pull'
60
+ docker-compose build
61
+ ```
62
+
40
63
  ## Usage
41
64
 
42
65
 
data/docker-compose.yml CHANGED
@@ -1,7 +1,7 @@
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
5
  build: .
6
6
  image: soar-authentication-token
7
7
  volumes:
@@ -2,20 +2,22 @@ require 'rack'
2
2
 
3
3
  module SoarAuthenticationToken
4
4
  class RackMiddleware
5
- def initialize(app, configuration)
5
+ def initialize(app, configuration, auditing = nil)
6
6
  @app = app
7
7
  @configuration = configuration
8
+ @auditing = auditing
8
9
  end
9
10
 
10
11
  def call(env)
11
12
  request = Rack::Request.new env
12
13
  session, params = request.session, request.params
13
- valid, token_meta = validate_and_resolve_token(request.env['HTTP_AUTHORIZATION'],params['flow_identifier'])
14
- if valid
14
+ token_valid, token_meta, message = validate_and_resolve_token(request.env['HTTP_AUTHORIZATION'],params['flow_identifier'])
15
+ if token_valid
15
16
  session['user'] = token_meta['authenticated_identifier']
16
17
  session['auth_token_meta'] = token_meta
17
18
  @app.call env
18
19
  else
20
+ audit_token_rejection("Token rejected due to #{message}",params['flow_identifier'])
19
21
  [401, {"Content-Type" => "text/html"}, ["401 - Not authenticated"]]
20
22
  end
21
23
  end
@@ -26,5 +28,9 @@ module SoarAuthenticationToken
26
28
  token_validator = SoarAuthenticationToken::TokenValidator.new(@configuration)
27
29
  token_validator.validate(authentication_token: authentication_token,flow_identifier: flow_identifier)
28
30
  end
31
+
32
+ def audit_token_rejection(message, flow_id)
33
+ @auditing.warn(message,flow_id) if @auditing
34
+ end
29
35
  end
30
36
  end
@@ -19,39 +19,66 @@ module SoarAuthenticationToken
19
19
  end
20
20
 
21
21
  def validate(authentication_token:,flow_identifier: nil)
22
- return validate_locally(authentication_token) if 'local' == @configuration['mode']
22
+ return validate_locally(authentication_token) if 'local' == @configuration['mode']
23
+ return validate_statically(authentication_token) if 'static' == @configuration['mode']
23
24
  return validate_remotely(authentication_token,flow_identifier)
24
25
  end
25
26
 
26
- def token(authentication_token:,flow_identifier: nil)
27
- return validate_locally(authentication_token) if 'local' == @configuration['mode']
28
- return validate_remotely(authentication_token,flow_identifier)
27
+ private
28
+
29
+ def validate_statically(authentication_token)
30
+ found_static_token = find_configured_static_token(authentication_token)
31
+ return rejection_result(reason: 'Unknown static token') if found_static_token.nil?
32
+ meta = compile_meta(token_identifier: 'static_token',
33
+ authenticated_identifier: found_static_token['authenticated_identifier'],
34
+ token_issue_time: found_static_token['token_issue_time'],
35
+ token_expiry_time: found_static_token['token_expiry_time'])
36
+ return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
37
+ return success_result(token_meta: meta)
38
+
29
39
  end
30
40
 
31
- private
41
+ def find_configured_static_token(authentication_token)
42
+ @configuration['static_tokens'].each { |static_token|
43
+ if authentication_token == static_token['token']
44
+ return static_token
45
+ end
46
+ }
47
+ nil
48
+ end
32
49
 
33
50
  def validate_locally(authentication_token)
34
- meta = token_meta(authentication_token)
35
- return [false, nil] if token_expired?(meta)
36
- return [false, nil] unless token_exist_in_store?(meta)
37
- [true, meta]
51
+ meta = decode_token_meta(authentication_token)
52
+ return rejection_result(reason: "Expired token <#{meta['token_expiry_time']}> for <#{meta['authenticated_identifier']}>") if token_expired?(meta)
53
+ return rejection_result(reason: "Unknown token for <#{meta['authenticated_identifier']}>") unless token_exist_in_store?(meta)
54
+ success_result(token_meta: meta)
38
55
  rescue JWT::VerificationError, JWT::DecodeError
39
- [false, nil]
56
+ rejection_result(reason: 'Token decode/verification failure')
40
57
  end
41
58
 
42
- def token_meta(authentication_token)
59
+ def decode_token_meta(authentication_token)
43
60
  decoded_token_payload = decode(authentication_token)
61
+ compile_meta(token_identifier: decoded_token_payload[0]['token_identifier'],
62
+ authenticated_identifier: decoded_token_payload[0]['authenticated_identifier'],
63
+ token_issue_time: decoded_token_payload[0]['token_issue_time'],
64
+ token_expiry_time: decoded_token_payload[0]['token_expiry_time'])
65
+ end
66
+
67
+ def compile_meta(token_identifier:,
68
+ authenticated_identifier:,
69
+ token_issue_time:,
70
+ token_expiry_time:)
44
71
  {
45
- 'token_identifier' => decoded_token_payload[0]['token_identifier'],
46
- 'authenticated_identifier' => decoded_token_payload[0]['authenticated_identifier'],
47
- 'token_issue_time' => decoded_token_payload[0]['token_issue_time'],
48
- 'token_expiry_time' => decoded_token_payload[0]['token_expiry_time'],
49
- 'token_age' => token_age(decoded_token_payload[0]['token_issue_time'])
72
+ 'token_identifier' => token_identifier,
73
+ 'authenticated_identifier' => authenticated_identifier,
74
+ 'token_issue_time' => token_issue_time,
75
+ 'token_expiry_time' => token_expiry_time,
76
+ 'token_age' => token_age(token_issue_time)
50
77
  }
51
78
  end
52
79
 
53
80
  def token_age(token_issue_time)
54
- Time.now - Time.parse(token_issue_time)
81
+ Time.now - Time.parse(token_issue_time.to_s)
55
82
  end
56
83
 
57
84
  def validate_remotely(authentication_token,flow_identifier)
@@ -72,24 +99,34 @@ module SoarAuthenticationToken
72
99
  body = JSON.parse(response.body)
73
100
  if 'success' == body['status']
74
101
  raise 'Token validation service did not provide authenticated_identifier' if body['data'].nil? or body['data']['authenticated_identifier'].nil?
75
- return [true, body['data']]
102
+ return success_result(token_meta: body['data'])
76
103
  end
77
104
  if 'fail' == body['status']
78
- return [false, nil]
105
+ return rejection_result(reason: 'remote validation failed')
79
106
  end
80
107
  raise "Failure validating token with token validation service. Status '#{body['status']}' received"
81
108
  end
82
109
 
83
110
  def validate_configuration
84
111
  raise "'mode' must be configured" unless @configuration['mode']
85
- raise "'mode' must be configured as either 'local' or 'remote'" unless ['local','remote'].include?(@configuration['mode'])
86
- if 'remote' == @configuration['mode']
87
- raise "'validator-url' must be configured in remote mode" unless @configuration['validator-url']
88
- else
89
- raise "'public_key' must be configured in local mode" unless @configuration['public_key']
90
- raise "'expiry' must be configured in local mode" unless @configuration['expiry']
91
- raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
92
- end
112
+ raise "'mode' must be configured as either 'local', 'remote' or 'static'" unless ['local','remote', 'static'].include?(@configuration['mode'])
113
+ validate_remote_configuration if 'remote' == @configuration['mode']
114
+ validate_local_configuration if 'local' == @configuration['mode']
115
+ validate_static_configuration if 'static' == @configuration['mode']
116
+ end
117
+
118
+ def validate_remote_configuration
119
+ raise "'validator-url' must be configured in remote mode" unless @configuration['validator-url']
120
+ end
121
+
122
+ def validate_local_configuration
123
+ raise "'public_key' must be configured in local mode" unless @configuration['public_key']
124
+ raise "'expiry' must be configured in local mode" unless @configuration['expiry']
125
+ raise "'expiry' must be an integer" unless Integer(@configuration['expiry'])
126
+ end
127
+
128
+ def validate_static_configuration
129
+ raise "'static_tokens' must be configured in local mode" unless @configuration['static_tokens']
93
130
  end
94
131
 
95
132
  def merge_with_default_configuration(configuration)
@@ -101,7 +138,7 @@ module SoarAuthenticationToken
101
138
  end
102
139
 
103
140
  def token_expired?(meta)
104
- Time.parse(meta['token_expiry_time']) < Time.now
141
+ Time.parse(meta['token_expiry_time'].to_s) < Time.now
105
142
  end
106
143
 
107
144
  def token_exist_in_store?(meta)
@@ -111,5 +148,13 @@ module SoarAuthenticationToken
111
148
  token_issue_time: meta['token_issue_time'],
112
149
  token_expiry_time: meta['token_expiry_time'])
113
150
  end
151
+
152
+ def rejection_result(reason:)
153
+ [false, nil, reason]
154
+ end
155
+
156
+ def success_result(token_meta:)
157
+ [true, token_meta, "Valid token for <#{token_meta['authenticated_identifier']}>" ]
158
+ end
114
159
  end
115
160
  end
@@ -1,3 +1,3 @@
1
1
  module SoarAuthenticationToken
2
- VERSION = '2.0.3'
2
+ VERSION = '3.0.0'
3
3
  end
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
  require 'yaml'
3
3
 
4
4
  describe SoarAuthenticationToken::TokenValidator do
5
+ subject { SoarAuthenticationToken::TokenValidator }
5
6
  before :all do
6
7
  @test_store = AuthTokenStoreProvider::StubClient.new
7
8
  keypair_generator = SoarAuthenticationToken::KeypairGenerator.new
@@ -16,6 +17,36 @@ describe SoarAuthenticationToken::TokenValidator do
16
17
  'mode' => 'local',
17
18
  'private_key' => @invalid_private_key
18
19
  }
20
+
21
+ @token_for_client_service_1 = 'some_secret_token_string_1111'
22
+ @token_for_client_service_2 = 'some_secret_token_string_2222'
23
+ @token_for_client_service_3_expired = 'some_secret_token_string_3333_expired'
24
+ @token_for_client_service_3_unknown = 'some_secret_token_string_3333_unknown'
25
+
26
+ current_time = Time.now
27
+ @static_validator_configuration = {
28
+ 'mode' => 'static',
29
+ 'static_tokens' => [
30
+ {
31
+ 'token' => 'some_secret_token_string_1111',
32
+ 'authenticated_identifier' => 'calling_client_service_1',
33
+ 'token_issue_time' => current_time.utc.iso8601(3),
34
+ 'token_expiry_time' => (current_time + 100).utc.iso8601(3)
35
+ },
36
+ {
37
+ 'token' => 'some_secret_token_string_2222',
38
+ 'authenticated_identifier' => 'calling_client_service_2',
39
+ 'token_issue_time' => current_time.utc.iso8601(3),
40
+ 'token_expiry_time' => (current_time + 100).utc.iso8601(3)
41
+ },
42
+ {
43
+ 'token' => 'some_secret_token_string_3333_expired',
44
+ 'authenticated_identifier' => 'calling_client_service_3',
45
+ 'token_issue_time' => (current_time - 100).utc.iso8601(3),
46
+ 'token_expiry_time' => (current_time - 50).utc.iso8601(3)
47
+ }
48
+ ]
49
+ }
19
50
  @local_validator_configuration = {
20
51
  'mode' => 'local',
21
52
  'public_key' => @valid_public_key
@@ -39,6 +70,8 @@ describe SoarAuthenticationToken::TokenValidator do
39
70
  @iut_local = SoarAuthenticationToken::TokenValidator.new(@local_validator_configuration)
40
71
  @iut_local.inject_store_provider(@test_store)
41
72
  @iut_remote = SoarAuthenticationToken::TokenValidator.new(@remote_validator_configuration)
73
+
74
+ @iut_static = SoarAuthenticationToken::TokenValidator.new(@static_validator_configuration)
42
75
  end
43
76
 
44
77
  after :each do
@@ -48,31 +81,35 @@ describe SoarAuthenticationToken::TokenValidator do
48
81
  expect(SoarAuthenticationToken::VERSION).not_to be nil
49
82
  end
50
83
 
84
+
51
85
  context "when validating a token locally using the configured public key" do
52
86
  it 'should indicate valid if the token is valid' do
53
87
  token, token_generator_meta = @local_valid_generator.generate(authenticated_identifier: @test_identifier)
54
- token_validity, token_meta = @iut_local.validate(authentication_token: token)
88
+ token_validity, token_meta, message = @iut_local.validate(authentication_token: token)
55
89
  expect(token_validity).to eq true
56
90
  end
57
91
 
58
- it 'should indicate invalid if the token is invalid' do
92
+ it 'should indicate invalid if the token is not valid' do
59
93
  token, token_generator_meta = @local_invalid_generator.generate(authenticated_identifier: @test_identifier)
60
- token_validity, token_meta = @iut_local.validate(authentication_token: token)
94
+ token_validity, token_meta, message = @iut_local.validate(authentication_token: token)
61
95
  expect(token_validity).to eq false
62
96
  end
63
97
 
64
- it 'should provide the authenticated_identifier if the token is valid' do
98
+ it 'should provide the token meta if the token is valid' do
65
99
  token, token_generator_meta = @local_valid_generator.generate(authenticated_identifier: @test_identifier)
66
- token_validity, token_meta = @iut_local.validate(authentication_token: token)
100
+ token_validity, token_meta, message = @iut_local.validate(authentication_token: token)
67
101
  expect(token_meta['authenticated_identifier']).to eq @test_identifier
68
102
  end
69
103
 
70
- it 'should not provide the authenticated_identifier if the token is invalid' do
104
+ it 'should not provide the token meta if the token is invalid' do
71
105
  token, token_generator_meta = @local_invalid_generator.generate(authenticated_identifier: @test_identifier)
72
- token_validity, token_meta = @iut_local.validate(authentication_token: token)
106
+ token_validity, token_meta, message = @iut_local.validate(authentication_token: token)
73
107
  expect(token_meta).to eq nil
74
108
  end
75
109
 
110
+ it 'should provide a message indicating that that token is valid if the token is valid' do
111
+ end
112
+
76
113
  it 'should indicate as invalid tokens that are older than the configured expiry time' do
77
114
  #TODO
78
115
  #expect(true).to eq false
@@ -82,30 +119,97 @@ describe SoarAuthenticationToken::TokenValidator do
82
119
  #TODO
83
120
  #expect(true).to eq false
84
121
  end
122
+
123
+ it 'should indicate as invalid tokens that are not in the token store' do
124
+ end
125
+ end
126
+
127
+
128
+ describe "#validate" do
129
+ context "given that the validator is configured for static tokens" do
130
+ let(:iut) { subject.new(@static_validator_configuration) }
131
+ context "given a token that is in the list of static tokens and not expired" do
132
+ let(:response) { iut.validate(authentication_token: @token_for_client_service_1) }
133
+ let(:token_validity) { response[0] }
134
+ let(:token_meta) { response[1] }
135
+ let(:message) { response[2] }
136
+
137
+ it 'should respond with true indicating token is valid' do
138
+ expect(token_validity).to eq true
139
+ end
140
+
141
+ it 'should respond with token meta' do
142
+ expect(token_meta).to_not eq nil
143
+ end
144
+
145
+ it 'should respond with a message indicating that the token is valid' do
146
+ expect(message).to eq 'Valid token for <calling_client_service_1>'
147
+ end
148
+ end
149
+
150
+ context "the token is in the list of static tokens and expired" do
151
+ let(:response) { @iut_static.validate(authentication_token: @token_for_client_service_3_expired) }
152
+ let(:token_validity) { response[0] }
153
+ let(:token_meta) { response[1] }
154
+ let(:message) { response[2] }
155
+
156
+ it 'should respond with false indicating token is invalid' do
157
+ expect(token_validity).to eq false
158
+ end
159
+
160
+ it 'should respond with nil token meta' do
161
+ expect(token_meta).to eq nil
162
+ end
163
+
164
+ it 'should respond with a message indicating that the token is expired' do
165
+ expect(message).to match /Expired token/
166
+ end
167
+ end
168
+
169
+ context "given a token that is not in the list of static tokens" do
170
+ let(:response) { @iut_static.validate(authentication_token: @token_for_client_service_3_unknown) }
171
+ let(:token_validity) { response[0] }
172
+ let(:token_meta) { response[1] }
173
+ let(:message) { response[2] }
174
+
175
+ it 'should respond with false indicating token is invalid' do
176
+ expect(token_validity).to eq false
177
+ end
178
+
179
+ it 'should respond with no token meta' do
180
+ expect(token_meta).to eq nil
181
+ end
182
+
183
+ it 'should respond with a message indicating that the token is valid' do
184
+ expect(message).to match /Unknown static token/
185
+ end
186
+ end
187
+ end
85
188
  end
86
189
 
190
+
87
191
  context "when validating a token remotely using the configured url" do
88
192
  it 'should indicate valid if the token is valid' do
89
193
  token, token_generator_meta = @remote_generator.generate(authenticated_identifier: @test_identifier)
90
- token_validity, token_meta = @iut_remote.validate(authentication_token: token)
194
+ token_validity, token_meta, message = @iut_remote.validate(authentication_token: token)
91
195
  expect(token_validity).to eq true
92
196
  end
93
197
 
94
198
  it 'should indicate invalid if the token is invalid' do
95
199
  token, token_generator_meta = @local_invalid_generator.generate(authenticated_identifier: @test_identifier)
96
- token_validity, token_meta = @iut_remote.validate(authentication_token: token)
200
+ token_validity, token_meta, message = @iut_remote.validate(authentication_token: token)
97
201
  expect(token_validity).to eq false
98
202
  end
99
203
 
100
204
  it 'should provide the authenticated_identifier if the token is valid' do
101
205
  token, token_generator_meta = @remote_generator.generate(authenticated_identifier: @test_identifier)
102
- token_validity, token_meta = @iut_remote.validate(authentication_token: token)
206
+ token_validity, token_meta, message = @iut_remote.validate(authentication_token: token)
103
207
  expect(token_meta['authenticated_identifier']).to eq @test_identifier
104
208
  end
105
209
 
106
210
  it 'should not provide the authenticated_identifier if the token is invalid' do
107
211
  token, token_generator_meta = @local_invalid_generator.generate(authenticated_identifier: @test_identifier)
108
- token_validity, token_meta = @iut_remote.validate(authentication_token: token)
212
+ token_validity, token_meta, message = @iut_remote.validate(authentication_token: token)
109
213
  expect(token_meta).to eq nil
110
214
  end
111
215
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: soar_authentication_token
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Barney de Villiers
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-16 00:00:00.000000000 Z
11
+ date: 2017-01-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: soar_xt