sso 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sso.rb +3 -3
- data/lib/sso/benchmarking.rb +6 -2
- data/lib/sso/client/authentications/passport.rb +2 -2
- data/lib/sso/client/passport_verifier.rb +9 -5
- data/lib/sso/client/warden/hooks/after_fetch.rb +31 -10
- data/lib/sso/client/warden/strategies/passport.rb +2 -2
- data/lib/sso/{server/configuration.rb → configuration.rb} +13 -1
- data/lib/sso/{server/configure.rb → configure.rb} +0 -0
- data/lib/sso/meter.rb +32 -0
- data/lib/sso/server/authentications/passport.rb +2 -2
- data/lib/sso/server/middleware/passport_destruction.rb +1 -1
- data/lib/sso/server/middleware/passport_exchange.rb +6 -6
- data/lib/sso/server/passport.rb +2 -2
- data/lib/sso/server/warden/strategies/passport.rb +3 -3
- data/spec/dummy/app/controllers/sessions_controller.rb +4 -3
- data/spec/lib/sso/benchmarking_spec.rb +83 -0
- data/spec/lib/sso/client/warden/hooks/after_fetch_spec.rb +190 -1
- data/spec/lib/sso/client/warden/strategies/passport_spec.rb +1 -1
- data/spec/spec_helper.rb +9 -0
- data/spec/support/sso/test/helpers.rb +11 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9660c9f78d14f395529c39a41a4355c7b0141cd
|
4
|
+
data.tar.gz: 56552af9e5cc37cae7ef084c68e80a0ac0a4d4c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b86244b2218c89bdac9433bf12c69ee0119e834cc1ec2c9d24a260e5121c05810a28cfbdb70cb012032383e59c1969e8322e5be21a7ef67b10973de0af66c8a5
|
7
|
+
data.tar.gz: 2823fc5f108f37bda20eaf72c75abaa70db5a270cb5522fa7d2dee388e57d26990d8a2010d2d283946bf3cef28d36a62be6254eea09b7f0fe3af9319185f30f4
|
data/lib/sso.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'operation'
|
2
2
|
|
3
3
|
require 'sso/logging'
|
4
|
+
require 'sso/meter'
|
4
5
|
require 'sso/benchmarking'
|
5
|
-
|
6
|
-
require 'sso/
|
7
|
-
require 'sso/server/configure'
|
6
|
+
require 'sso/configuration'
|
7
|
+
require 'sso/configure'
|
8
8
|
|
9
9
|
require 'sso/client/omniauth/strategies/sso'
|
10
10
|
|
data/lib/sso/benchmarking.rb
CHANGED
@@ -2,13 +2,17 @@ module SSO
|
|
2
2
|
# Helper to log results of benchmarks.
|
3
3
|
module Benchmarking
|
4
4
|
include ::SSO::Logging
|
5
|
+
include ::SSO::Meter
|
5
6
|
|
6
|
-
def benchmark(name, &block)
|
7
|
+
def benchmark(name: nil, metric: nil, &block)
|
8
|
+
return unless block_given?
|
7
9
|
result = nil
|
8
10
|
seconds = Benchmark.realtime do
|
9
11
|
result = block.call
|
10
12
|
end
|
11
|
-
|
13
|
+
milliseconds = (seconds * 1000).round
|
14
|
+
debug { "#{name || metric || 'Benchmark'} took #{milliseconds}ms" }
|
15
|
+
histogram key: metric, value: milliseconds if metric
|
12
16
|
result
|
13
17
|
end
|
14
18
|
end
|
@@ -85,7 +85,7 @@ module SSO
|
|
85
85
|
end
|
86
86
|
|
87
87
|
def failure_rack_array
|
88
|
-
payload = { success:
|
88
|
+
payload = { success: false, code: :passport_verification_failed }
|
89
89
|
[200, { 'Content-Type' => 'application/json' }, [payload.to_json]]
|
90
90
|
end
|
91
91
|
|
@@ -121,7 +121,7 @@ module SSO
|
|
121
121
|
end
|
122
122
|
|
123
123
|
def decrypt_chip!
|
124
|
-
benchmark 'Passport chip decryption' do
|
124
|
+
benchmark(name: 'Passport chip decryption') do
|
125
125
|
decipher = chip_digest
|
126
126
|
decipher.decrypt
|
127
127
|
decipher.key = chip_key
|
@@ -18,18 +18,22 @@ module SSO
|
|
18
18
|
fetch_response { |failure| return failure }
|
19
19
|
interpret_response
|
20
20
|
|
21
|
-
rescue JSON::ParserError
|
21
|
+
rescue ::JSON::ParserError
|
22
22
|
error { 'SSO Server response is not valid JSON.' }
|
23
23
|
error { response.inspect }
|
24
|
+
Operations.failure :server_response_not_parseable, object: response
|
25
|
+
end
|
26
|
+
|
27
|
+
def human_readable_timeout_in_ms
|
28
|
+
"#{timeout_in_milliseconds}ms"
|
24
29
|
end
|
25
30
|
|
26
31
|
private
|
27
32
|
|
28
33
|
def fetch_response
|
29
|
-
yield Operations.failure(:server_unreachable, object: response) unless response.code == 200
|
34
|
+
yield Operations.failure(:server_unreachable, object: response) unless response.code.to_s == '200'
|
30
35
|
yield Operations.failure(:server_response_not_parseable, object: response) unless parsed_response
|
31
36
|
yield Operations.failure(:server_response_missing_success_flag, object: response) unless response_has_success_flag?
|
32
|
-
yield Operations.failure(:server_response_unsuccessful, object: response) unless parsed_response['success'].to_s == 'true'
|
33
37
|
Operations.success :server_response_looks_legit
|
34
38
|
end
|
35
39
|
|
@@ -130,7 +134,7 @@ module SSO
|
|
130
134
|
|
131
135
|
def response!
|
132
136
|
debug { "Fetching Passport from #{endpoint.inspect}" }
|
133
|
-
benchmark 'Passport
|
137
|
+
benchmark(name: 'Passport verification request', metric: 'client.passport.verification.duration') do
|
134
138
|
::HTTParty.get endpoint, timeout: timeout_in_seconds, query: query_params, headers: { 'Accept' => 'application/json' }
|
135
139
|
end
|
136
140
|
end
|
@@ -140,7 +144,7 @@ module SSO
|
|
140
144
|
end
|
141
145
|
|
142
146
|
def response_has_success_flag?
|
143
|
-
parsed_response && parsed_response.respond_to?(:key?) && parsed_response.key?('success')
|
147
|
+
parsed_response && parsed_response.respond_to?(:key?) && (parsed_response.key?('success') || parsed_response.key?(:success))
|
144
148
|
end
|
145
149
|
|
146
150
|
end
|
@@ -12,6 +12,7 @@ module SSO
|
|
12
12
|
class AfterFetch
|
13
13
|
include ::SSO::Logging
|
14
14
|
include ::SSO::Benchmarking
|
15
|
+
include ::SSO::Meter
|
15
16
|
|
16
17
|
attr_reader :passport, :warden, :options
|
17
18
|
delegate :request, to: :warden
|
@@ -31,12 +32,14 @@ module SSO
|
|
31
32
|
return unless passport.is_a?(::SSO::Client::Passport)
|
32
33
|
verify
|
33
34
|
|
34
|
-
rescue Timeout::Error
|
35
|
+
rescue ::Timeout::Error
|
35
36
|
error { 'SSO Server timed out. Continuing with last known authentication/authorization...' }
|
36
|
-
|
37
|
+
meter :timeout, timeout_ms: verifier.human_readable_timeout_in_ms
|
38
|
+
Operations.failure :server_request_timed_out
|
37
39
|
|
38
40
|
rescue => exception
|
39
41
|
::SSO.config.exception_handler.call exception
|
42
|
+
Operations.failure :client_exception_caught
|
40
43
|
end
|
41
44
|
|
42
45
|
private
|
@@ -53,14 +56,17 @@ module SSO
|
|
53
56
|
verification.code
|
54
57
|
end
|
55
58
|
|
59
|
+
def verification_object
|
60
|
+
verification.object
|
61
|
+
end
|
62
|
+
|
56
63
|
def verify
|
57
64
|
debug { "Validating Passport #{passport.id.inspect} of logged in #{passport.user.class} in scope #{warden_scope.inspect}" }
|
58
65
|
|
59
66
|
case verification_code
|
60
67
|
when :server_unreachable then server_unreachable!
|
61
68
|
when :server_response_not_parseable then server_response_not_parseable!
|
62
|
-
when :server_response_missing_success_flag
|
63
|
-
when :server_response_unsuccessful! then server_response_unsuccessful!
|
69
|
+
when :server_response_missing_success_flag then server_response_missing_success_flag!
|
64
70
|
when :passport_valid then passport_valid!
|
65
71
|
when :passport_valid_and_modified then passport_valid_and_modified!(verification.object)
|
66
72
|
when :passport_invalid then passport_invalid!
|
@@ -74,39 +80,54 @@ module SSO
|
|
74
80
|
passport.modified!
|
75
81
|
passport.user = modified_passport.user
|
76
82
|
passport.state = modified_passport.state
|
77
|
-
|
83
|
+
meter :valid_and_modified
|
84
|
+
Operations.success :valid_and_modified
|
78
85
|
end
|
79
86
|
|
80
87
|
def passport_valid!
|
81
88
|
debug { 'Valid passport, no changes' }
|
82
89
|
passport.verified!
|
83
|
-
|
90
|
+
meter :valid
|
91
|
+
Operations.success :valid
|
84
92
|
end
|
85
93
|
|
86
94
|
def passport_invalid!
|
87
95
|
info { 'Your Passport is not valid any more.' }
|
88
96
|
warden.logout warden_scope
|
89
|
-
|
97
|
+
meter :invalid
|
98
|
+
Operations.failure :invalid
|
90
99
|
end
|
91
100
|
|
92
101
|
def server_unreachable!
|
93
|
-
error { "SSO Server responded with an unexpected HTTP status code (#{
|
102
|
+
error { "SSO Server responded with an unexpected HTTP status code (#{verification_code.inspect} instead of 200). #{verification_object.inspect}" }
|
103
|
+
meter :server_unreachable
|
104
|
+
Operations.failure :server_unreachable
|
94
105
|
end
|
95
106
|
|
96
107
|
def server_response_missing_success_flag!
|
97
108
|
error { 'SSO Server response did not include the expected success flag.' }
|
109
|
+
meter :server_response_missing_success_flag
|
110
|
+
Operations.failure :server_response_missing_success_flag
|
98
111
|
end
|
99
112
|
|
100
113
|
def unexpected_server_response_status!
|
101
114
|
error { "SSO Server response did not include a known passport status code. #{verification_code.inspect}" }
|
115
|
+
meter :unexpected_server_response_status
|
116
|
+
Operations.failure :unexpected_server_response_status
|
102
117
|
end
|
103
118
|
|
104
119
|
def server_response_not_parseable!
|
105
120
|
error { 'SSO Server response could not be parsed at all.' }
|
121
|
+
meter :server_response_not_parseable
|
122
|
+
Operations.failure :server_response_not_parseable
|
106
123
|
end
|
107
124
|
|
108
|
-
def meter(
|
109
|
-
|
125
|
+
def meter(key, data = {})
|
126
|
+
options[:key] = "client.warden.hooks.after_fetch.#{key}"
|
127
|
+
options[:tags] = { scope: warden_scope }
|
128
|
+
data[:passport_id] = passport.id
|
129
|
+
options[:data] = data
|
130
|
+
track options
|
110
131
|
end
|
111
132
|
|
112
133
|
# TODO: Use ActionDispatch remote IP or you might get the Load Balancer's IP instead :(
|
@@ -21,7 +21,7 @@ module SSO
|
|
21
21
|
debug { "Persisting trusted Passport #{authentication.object.inspect}" }
|
22
22
|
success! authentication.object
|
23
23
|
else
|
24
|
-
debug { 'Authentication from Passport failed.' }
|
24
|
+
debug { 'Authentication from Passport on Client failed.' }
|
25
25
|
debug { "Responding with #{authentication.object.inspect}" }
|
26
26
|
custom! authentication.object
|
27
27
|
end
|
@@ -31,7 +31,7 @@ module SSO
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def passport_authentication
|
34
|
-
benchmark 'Passport proxy verification' do
|
34
|
+
benchmark(name: 'Passport proxy verification request', metric: 'client.passport.verification') do
|
35
35
|
::SSO::Client::Authentications::Passport.new(request).authenticate
|
36
36
|
end
|
37
37
|
end
|
@@ -2,6 +2,7 @@ require 'logger'
|
|
2
2
|
|
3
3
|
module SSO
|
4
4
|
class Configuration
|
5
|
+
include ::SSO::Logging
|
5
6
|
|
6
7
|
# Server
|
7
8
|
|
@@ -54,6 +55,11 @@ module SSO
|
|
54
55
|
end
|
55
56
|
attr_writer :exception_handler
|
56
57
|
|
58
|
+
def metric
|
59
|
+
@metric || default_metric
|
60
|
+
end
|
61
|
+
attr_writer :metric
|
62
|
+
|
57
63
|
def passport_chip_key
|
58
64
|
@passport_chip_key || fail('You need to configure a secret passport_chip_key, see SSO::Configuration for more info.')
|
59
65
|
end
|
@@ -110,8 +116,14 @@ module SSO
|
|
110
116
|
end
|
111
117
|
end
|
112
118
|
|
119
|
+
def default_metric
|
120
|
+
proc do |type:, key:, value:, tags:, data:|
|
121
|
+
debug { "Measuring #{type.inspect} with key #{key.inspect} and value #{value.inspect} and tags #{tags} and data #{data}" }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
113
125
|
def default_session_backend
|
114
|
-
fail('You need to configure session_backend, see SSO::Configuration for more info.') unless %w(
|
126
|
+
fail('You need to configure session_backend, see SSO::Configuration for more info.') unless %w(development test).include?(environment)
|
115
127
|
end
|
116
128
|
|
117
129
|
def default_passport_verification_timeout_ms
|
File without changes
|
data/lib/sso/meter.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module SSO
|
2
|
+
module Meter
|
3
|
+
include ::SSO::Logging
|
4
|
+
|
5
|
+
def track(key:, value: 1, tags: nil, data: {})
|
6
|
+
data[:caller] = caller_name
|
7
|
+
# info { "Measuring increment #{key.inspect} with value #{value.inspect} and tags #{tags} and data #{data}" }
|
8
|
+
metric.call type: :increment, key: "sso.#{key}", value: value, tags: tags, data: data
|
9
|
+
|
10
|
+
rescue => exception
|
11
|
+
::SSO.config.exception_handler.call exception
|
12
|
+
end
|
13
|
+
|
14
|
+
def histogram(key:, value:, tags: nil, data: {})
|
15
|
+
data[:caller] = caller_name
|
16
|
+
# info { "Measuring histogram #{key.inspect} with value #{value.inspect} and tags #{tags} and data #{data}" }
|
17
|
+
metric.call type: :histogram, key: "sso.#{key}", value: value, tags: tags, data: data
|
18
|
+
|
19
|
+
rescue => exception
|
20
|
+
::SSO.config.exception_handler.call exception
|
21
|
+
end
|
22
|
+
|
23
|
+
def caller_name
|
24
|
+
self.class.name
|
25
|
+
end
|
26
|
+
|
27
|
+
def metric
|
28
|
+
::SSO.config.metric
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -15,7 +15,7 @@ module SSO
|
|
15
15
|
result
|
16
16
|
else
|
17
17
|
# TODO: Prevent Flooding here.
|
18
|
-
debug { "The Passport authentication failed: #{result.code}" }
|
18
|
+
debug { "The Server Passport authentication failed: #{result.code}" }
|
19
19
|
Operations.failure :passport_authentication_failed, object: failure_rack_array
|
20
20
|
end
|
21
21
|
end
|
@@ -73,7 +73,7 @@ module SSO
|
|
73
73
|
# all passports simply because a load balancer is pointing to the wrong Rails application or something.
|
74
74
|
#
|
75
75
|
def failure_rack_array
|
76
|
-
payload = { success:
|
76
|
+
payload = { success: false, code: :passport_invalid }
|
77
77
|
[200, { 'Content-Type' => 'application/json' }, [payload.to_json]]
|
78
78
|
end
|
79
79
|
|
@@ -27,7 +27,7 @@ module SSO
|
|
27
27
|
end
|
28
28
|
|
29
29
|
def json_code(code)
|
30
|
-
[200, { 'Content-Type' => 'application/json' }, [{ success:
|
30
|
+
[200, { 'Content-Type' => 'application/json' }, [{ success: false, code: code }.to_json]]
|
31
31
|
end
|
32
32
|
|
33
33
|
def passports_path
|
@@ -19,16 +19,16 @@ module SSO
|
|
19
19
|
end
|
20
20
|
|
21
21
|
token = request.params['access_token']
|
22
|
-
debug { "Detected incoming Passport
|
22
|
+
debug { "Detected incoming Passport exchange request for access token #{token.inspect}" }
|
23
23
|
access_token = ::Doorkeeper::AccessToken.find_by_token token
|
24
24
|
|
25
|
-
return
|
26
|
-
return
|
25
|
+
return json_error :access_token_not_found unless access_token
|
26
|
+
return json_error :access_token_invalid unless access_token.valid?
|
27
27
|
|
28
28
|
finding = ::SSO::Server::Passports.find_by_access_token_id(access_token.id)
|
29
29
|
if finding.failure?
|
30
30
|
# This should never happen. Every Access Token should be connected to a Passport.
|
31
|
-
return
|
31
|
+
return json_error :passport_not_found
|
32
32
|
end
|
33
33
|
passport = finding.object
|
34
34
|
|
@@ -44,8 +44,8 @@ module SSO
|
|
44
44
|
[200, { 'Content-Type' => 'application/json' }, [payload.to_json]]
|
45
45
|
end
|
46
46
|
|
47
|
-
def
|
48
|
-
[200, { 'Content-Type' => 'application/json' }, [{ success:
|
47
|
+
def json_error(code)
|
48
|
+
[200, { 'Content-Type' => 'application/json' }, [{ success: false, code: code }.to_json]]
|
49
49
|
end
|
50
50
|
|
51
51
|
def passports_path
|
data/lib/sso/server/passport.rb
CHANGED
@@ -46,7 +46,7 @@ module SSO
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def state!
|
49
|
-
result = benchmark 'Passport user state calculation' do
|
49
|
+
result = benchmark(name: 'Passport user state calculation') do
|
50
50
|
OpenSSL::HMAC.hexdigest user_state_digest, user_state_key, user_state_base
|
51
51
|
end
|
52
52
|
debug { "The user state is #{result.inspect}" }
|
@@ -62,7 +62,7 @@ module SSO
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def chip!
|
65
|
-
benchmark 'Passport chip encryption' do
|
65
|
+
benchmark(name: 'Passport chip encryption') do
|
66
66
|
ensure_secret
|
67
67
|
cipher = chip_digest
|
68
68
|
cipher.encrypt
|
@@ -20,8 +20,8 @@ module SSO
|
|
20
20
|
debug { "Responding with #{authentication.object}" }
|
21
21
|
custom! authentication.object
|
22
22
|
else
|
23
|
-
debug { 'Authentication from Passport failed.' }
|
24
|
-
|
23
|
+
debug { 'Authentication from Passport on Server failed.' }
|
24
|
+
custom! authentication.object
|
25
25
|
end
|
26
26
|
|
27
27
|
rescue => exception
|
@@ -29,7 +29,7 @@ module SSO
|
|
29
29
|
end
|
30
30
|
|
31
31
|
def passport_authentication
|
32
|
-
benchmark 'Passport verification' do
|
32
|
+
benchmark(name: 'Passport verification') do
|
33
33
|
::SSO::Server::Authentications::Passport.new(request).authenticate
|
34
34
|
end
|
35
35
|
end
|
@@ -2,7 +2,7 @@ class SessionsController < ApplicationController
|
|
2
2
|
include ::SSO::Logging
|
3
3
|
delegate :logout, to: :warden
|
4
4
|
|
5
|
-
before_action :
|
5
|
+
before_action :prevent_json, only: [:new]
|
6
6
|
|
7
7
|
# POI
|
8
8
|
def new
|
@@ -27,8 +27,9 @@ class SessionsController < ApplicationController
|
|
27
27
|
|
28
28
|
private
|
29
29
|
|
30
|
-
def
|
31
|
-
return unless request.format == :json
|
30
|
+
def prevent_json
|
31
|
+
return unless request.format.to_sym == :json
|
32
|
+
warn { "This request is asking for JSON where it shouldn't" }
|
32
33
|
render status: :unauthorized, json: { status: :error, code: :authentication_failed }
|
33
34
|
end
|
34
35
|
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe ::SSO::Benchmarking do
|
4
|
+
|
5
|
+
let(:instance) { MyTestNamespace::MyClass.new }
|
6
|
+
|
7
|
+
before do
|
8
|
+
stub_const 'MyTestNamespace', Module.new
|
9
|
+
stub_const 'MyTestNamespace::MyClass', Class.new { include SSO::Benchmarking }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#benchmark' do
|
13
|
+
context 'without block' do
|
14
|
+
it 'is nil' do
|
15
|
+
expect(instance.benchmark).to be_nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'block given' do
|
20
|
+
context 'without arguments' do
|
21
|
+
it 'returns what was passed in' do
|
22
|
+
duration = instance.benchmark { :something }
|
23
|
+
expect(duration).to eq :something
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'logs' do
|
27
|
+
expect(instance).to receive(:debug) do |_, &block|
|
28
|
+
expect(block.call).to eq 'Benchmark took 0ms'
|
29
|
+
end
|
30
|
+
instance.benchmark {}
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'does not meter' do
|
34
|
+
expect(::SSO.config).to_not receive(:metric)
|
35
|
+
instance.benchmark {}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'only with name' do
|
40
|
+
it 'logs with the name' do
|
41
|
+
expect(instance).to receive(:debug) do |_, &block|
|
42
|
+
expect(block.call).to eq 'Long calculation took 0ms'
|
43
|
+
end
|
44
|
+
instance.benchmark(name: 'Long calculation') {}
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'does not meter' do
|
48
|
+
expect(instance).to_not receive(:histogram)
|
49
|
+
instance.benchmark(name: 'Long calculation') {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'only with metric' do
|
54
|
+
it 'logs with the metric' do
|
55
|
+
expect(instance).to receive(:debug) do |_, &block|
|
56
|
+
expect(block.call).to eq 'blob.serialization took 0ms'
|
57
|
+
end
|
58
|
+
instance.benchmark(metric: 'blob.serialization') {}
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'meters as histogram with the metric as name' do
|
62
|
+
expect(instance).to receive(:histogram).with key: 'blob.serialization', value: 0
|
63
|
+
instance.benchmark(metric: 'blob.serialization') {}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with name and metric' do
|
68
|
+
it 'logs with the name' do
|
69
|
+
expect(instance).to receive(:debug) do |_, &block|
|
70
|
+
expect(block.call).to eq 'Synchronous encryption took 0ms'
|
71
|
+
end
|
72
|
+
instance.benchmark(name: 'Synchronous encryption', metric: 'encryption.aes') {}
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'meters as histogram with the metric as name' do
|
76
|
+
expect(instance).to receive(:histogram).with key: 'encryption.aes', value: 0
|
77
|
+
instance.benchmark(name: 'Synchronous encryption', metric: 'encryption.aes') {}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true do
|
3
|
+
RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true, stub_benchmarks: true do
|
4
4
|
|
5
5
|
# Client side
|
6
6
|
let(:warden_env) { {} }
|
@@ -10,6 +10,7 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
10
10
|
let(:hook) { described_class.new passport: client_passport, warden: warden, options: {} }
|
11
11
|
let(:client_user) { double :client_user, name: 'Good old client user' }
|
12
12
|
let(:client_passport) { ::SSO::Client::Passport.new id: passport_id, secret: passport_secret, state: passport_state, user: client_user }
|
13
|
+
let(:operation) { hook.call }
|
13
14
|
|
14
15
|
# Shared
|
15
16
|
let!(:oauth_app) { create :outsider_doorkeeper_application }
|
@@ -34,6 +35,10 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
34
35
|
context 'invalid passport' do
|
35
36
|
let(:passport_secret) { SecureRandom.uuid }
|
36
37
|
|
38
|
+
before do
|
39
|
+
expect(warden).to receive :logout
|
40
|
+
end
|
41
|
+
|
37
42
|
it 'does not verify the passport' do
|
38
43
|
expect(client_passport).to_not be_verified
|
39
44
|
hook.call
|
@@ -45,6 +50,20 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
45
50
|
hook.call
|
46
51
|
expect(client_passport).to_not be_modified
|
47
52
|
end
|
53
|
+
|
54
|
+
it 'fails' do
|
55
|
+
expect(operation).to be_failure
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'has a useful error code' do
|
59
|
+
expect(operation.code).to eq :invalid
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'meters the invalid passport' do
|
63
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
64
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.invalid', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
65
|
+
hook.call
|
66
|
+
end
|
48
67
|
end
|
49
68
|
|
50
69
|
context 'user does not change' do
|
@@ -64,6 +83,20 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
64
83
|
hook.call
|
65
84
|
expect(client_passport.user.name).to eq 'Good old client user'
|
66
85
|
end
|
86
|
+
|
87
|
+
it 'succeeds' do
|
88
|
+
expect(operation).to be_success
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'has a useful error code' do
|
92
|
+
expect(operation.code).to eq :valid
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'meters the invalid passport' do
|
96
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
97
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.valid', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
98
|
+
hook.call
|
99
|
+
end
|
67
100
|
end
|
68
101
|
|
69
102
|
context 'user attribute changed which is not included in the state digest' do
|
@@ -88,6 +121,20 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
88
121
|
hook.call
|
89
122
|
expect(client_passport.user.name).to eq 'Good old client user'
|
90
123
|
end
|
124
|
+
|
125
|
+
it 'succeeds' do
|
126
|
+
expect(operation).to be_success
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'has a useful error code' do
|
130
|
+
expect(operation.code).to eq :valid
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'meters the invalid passport' do
|
134
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
135
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.valid', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
136
|
+
hook.call
|
137
|
+
end
|
91
138
|
end
|
92
139
|
|
93
140
|
context 'user attribute changed which results in a new state digest' do
|
@@ -112,6 +159,148 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
|
|
112
159
|
hook.call
|
113
160
|
expect(client_passport.user['name']).to eq server_user.name
|
114
161
|
end
|
162
|
+
|
163
|
+
it 'succeeds' do
|
164
|
+
expect(operation).to be_success
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'has a useful error code' do
|
168
|
+
expect(operation.code).to eq :valid_and_modified
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'meters the invalid passport' do
|
172
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
173
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.valid_and_modified', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
174
|
+
hook.call
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'server request times out' do
|
179
|
+
before do
|
180
|
+
expect(::HTTParty).to receive(:get).and_raise ::Net::ReadTimeout
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'fails' do
|
184
|
+
expect(operation).to be_failure
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'has a useful error code' do
|
188
|
+
expect(operation.code).to eq :server_request_timed_out
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'meters the timeout' do
|
192
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.timeout', value: 1, tags: { scope: nil }, data: { timeout_ms: '100ms', passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
193
|
+
hook.call
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'server unreachable' do
|
198
|
+
before do
|
199
|
+
expect(::HTTParty).to receive(:get).and_return double(:response, code: 302)
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'fails' do
|
203
|
+
expect(operation).to be_failure
|
204
|
+
end
|
205
|
+
|
206
|
+
it 'has a useful error code' do
|
207
|
+
expect(operation.code).to eq :server_unreachable
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'meters the timeout' do
|
211
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
212
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.server_unreachable', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
213
|
+
hook.call
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'server response not parseable' do
|
218
|
+
let(:response) { double :response, code: 200 }
|
219
|
+
|
220
|
+
before do
|
221
|
+
expect(::HTTParty).to receive(:get).and_return response
|
222
|
+
allow(response).to receive(:parsed_response).and_raise ::JSON::ParserError
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'fails' do
|
226
|
+
expect(operation).to be_failure
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'has a useful error code' do
|
230
|
+
expect(operation.code).to eq :server_response_not_parseable
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'meters the timeout' do
|
234
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
235
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.server_response_not_parseable', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
236
|
+
hook.call
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
context 'server response has no success flag at all' do
|
241
|
+
let(:response) { double :response, code: 200, parsed_response: { some: :thing } }
|
242
|
+
|
243
|
+
before do
|
244
|
+
expect(::HTTParty).to receive(:get).and_return response
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'fails' do
|
248
|
+
expect(operation).to be_failure
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'has a useful error code' do
|
252
|
+
expect(operation.code).to eq :server_response_missing_success_flag
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'meters the timeout' do
|
256
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
257
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.server_response_missing_success_flag', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
258
|
+
hook.call
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
context 'server behaves weirdly' do
|
263
|
+
let(:response) { double :response, code: 200, parsed_response: { success: true } }
|
264
|
+
|
265
|
+
before do
|
266
|
+
expect(::HTTParty).to receive(:get).and_return response
|
267
|
+
end
|
268
|
+
|
269
|
+
it 'fails' do
|
270
|
+
expect(operation).to be_failure
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'has a useful error code' do
|
274
|
+
expect(operation.code).to eq :unexpected_server_response_status
|
275
|
+
end
|
276
|
+
|
277
|
+
it 'meters the timeout' do
|
278
|
+
expect(::SSO.config.metric).to receive(:call).with type: :histogram, key: 'sso.client.passport.verification.duration', value: 42_000, tags: nil, data: { caller: 'SSO::Client::PassportVerifier' }
|
279
|
+
expect(::SSO.config.metric).to receive(:call).with type: :increment, key: 'sso.client.warden.hooks.after_fetch.unexpected_server_response_status', value: 1, tags: { scope: nil }, data: { passport_id: client_passport.id, caller: 'SSO::Client::Warden::Hooks::AfterFetch' }
|
280
|
+
hook.call
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
context 'client-side exception' do
|
285
|
+
before do
|
286
|
+
expect(::HTTParty).to receive(:get).and_raise ArgumentError
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'fails' do
|
290
|
+
expect(operation).to be_failure
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'has a useful error code' do
|
294
|
+
expect(operation.code).to eq :client_exception_caught
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
describe '.activate' do
|
299
|
+
|
300
|
+
it 'proxies the options to warden' do
|
301
|
+
expect(::Warden::Manager).to receive(:after_fetch).with(scope: :insider).and_yield :passport, :warden, :options
|
302
|
+
described_class.activate scope: :insider
|
303
|
+
end
|
115
304
|
end
|
116
305
|
|
117
306
|
end
|
@@ -52,7 +52,7 @@ RSpec.describe SSO::Client::Warden::Strategies::Passport do
|
|
52
52
|
expect(rack_array.size).to eq 3
|
53
53
|
expect(rack_array[0]).to eq 200
|
54
54
|
expect(rack_array[1]).to eq 'Content-Type' => 'application/json'
|
55
|
-
expect(rack_array[2]).to eq ['{"success":
|
55
|
+
expect(rack_array[2]).to eq ['{"success":false,"code":"passport_verification_failed"}']
|
56
56
|
end
|
57
57
|
strategy.authenticate!
|
58
58
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -34,6 +34,11 @@ RSpec.configure do |config|
|
|
34
34
|
config.before :suite do
|
35
35
|
DatabaseCleaner.strategy = :transaction
|
36
36
|
DatabaseCleaner.clean_with :truncation
|
37
|
+
SSO.config.exception_handler = nil
|
38
|
+
SSO.config.passport_chip_key = nil
|
39
|
+
SSO.config.oauth_client_id = nil
|
40
|
+
SSO.config.oauth_client_secret = nil
|
41
|
+
SSO.config.metric = ::SSO::Test::Helpers.meter
|
37
42
|
end
|
38
43
|
|
39
44
|
config.before :each do
|
@@ -48,6 +53,10 @@ RSpec.configure do |config|
|
|
48
53
|
SSO.config.exception_handler = proc { |exception| fail exception }
|
49
54
|
end
|
50
55
|
|
56
|
+
config.before :each, stub_benchmarks: true do
|
57
|
+
stub_benchmarks
|
58
|
+
end
|
59
|
+
|
51
60
|
config.after :each do
|
52
61
|
Timecop.return
|
53
62
|
SSO.config.exception_handler = nil
|
@@ -4,6 +4,17 @@ module SSO
|
|
4
4
|
module Test
|
5
5
|
module Helpers
|
6
6
|
|
7
|
+
def self.meter
|
8
|
+
proc {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def stub_benchmarks
|
12
|
+
allow(Benchmark).to receive(:realtime) do |&block|
|
13
|
+
block.call
|
14
|
+
42
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
7
18
|
# Inspired by Warden::Spec::Helpers
|
8
19
|
def env_with_params(path = '/', params = {}, env = {})
|
9
20
|
method = params.delete(:method) || 'GET'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sso
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- halo
|
@@ -265,12 +265,13 @@ files:
|
|
265
265
|
- lib/sso/client/passport_verifier.rb
|
266
266
|
- lib/sso/client/warden/hooks/after_fetch.rb
|
267
267
|
- lib/sso/client/warden/strategies/passport.rb
|
268
|
+
- lib/sso/configuration.rb
|
269
|
+
- lib/sso/configure.rb
|
268
270
|
- lib/sso/logging.rb
|
271
|
+
- lib/sso/meter.rb
|
269
272
|
- lib/sso/server.rb
|
270
273
|
- lib/sso/server/README.md
|
271
274
|
- lib/sso/server/authentications/passport.rb
|
272
|
-
- lib/sso/server/configuration.rb
|
273
|
-
- lib/sso/server/configure.rb
|
274
275
|
- lib/sso/server/doorkeeper/access_token_marker.rb
|
275
276
|
- lib/sso/server/doorkeeper/grant_marker.rb
|
276
277
|
- lib/sso/server/doorkeeper/resource_owner_authenticator.rb
|
@@ -324,6 +325,7 @@ files:
|
|
324
325
|
- spec/dummy/db/schema.rb
|
325
326
|
- spec/integration/oauth/authorization_code_spec.rb
|
326
327
|
- spec/integration/oauth/password_spec.rb
|
328
|
+
- spec/lib/sso/benchmarking_spec.rb
|
327
329
|
- spec/lib/sso/client/authentications/passport_spec.rb
|
328
330
|
- spec/lib/sso/client/warden/hooks/after_fetch_spec.rb
|
329
331
|
- spec/lib/sso/client/warden/strategies/passport_spec.rb
|
@@ -403,6 +405,7 @@ test_files:
|
|
403
405
|
- spec/dummy/Rakefile
|
404
406
|
- spec/integration/oauth/authorization_code_spec.rb
|
405
407
|
- spec/integration/oauth/password_spec.rb
|
408
|
+
- spec/lib/sso/benchmarking_spec.rb
|
406
409
|
- spec/lib/sso/client/authentications/passport_spec.rb
|
407
410
|
- spec/lib/sso/client/warden/hooks/after_fetch_spec.rb
|
408
411
|
- spec/lib/sso/client/warden/strategies/passport_spec.rb
|