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 +4 -4
- data/README.md +23 -0
- data/docker-compose.yml +1 -1
- data/lib/soar_authentication_token/rack_middleware.rb +9 -3
- data/lib/soar_authentication_token/token_validator.rb +73 -28
- data/lib/soar_authentication_token/version.rb +1 -1
- data/spec/token_validator_spec.rb +115 -11
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1a6ee48994978d49a24f51df0e4a0fa78e9b48c
|
4
|
+
data.tar.gz: 40f3341de578f6cff681c5478e2a90b74bc9bdbb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
@@ -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
|
-
|
14
|
-
if
|
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)
|
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
|
-
|
27
|
-
|
28
|
-
|
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
|
-
|
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 =
|
35
|
-
return [
|
36
|
-
return [
|
37
|
-
|
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
|
-
|
56
|
+
rejection_result(reason: 'Token decode/verification failure')
|
40
57
|
end
|
41
58
|
|
42
|
-
def
|
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' =>
|
46
|
-
'authenticated_identifier' =>
|
47
|
-
'token_issue_time' =>
|
48
|
-
'token_expiry_time' =>
|
49
|
-
'token_age' => token_age(
|
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
|
102
|
+
return success_result(token_meta: body['data'])
|
76
103
|
end
|
77
104
|
if 'fail' == body['status']
|
78
|
-
return
|
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 '
|
86
|
-
if 'remote' == @configuration['mode']
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
@@ -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
|
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
|
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
|
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:
|
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-
|
11
|
+
date: 2017-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: soar_xt
|