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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5e1f2feee69f64105e38a748befeebe0de6d33a5
4
- data.tar.gz: c0637201a0d104c075dc7c049eef0d9f381e0596
3
+ metadata.gz: b9660c9f78d14f395529c39a41a4355c7b0141cd
4
+ data.tar.gz: 56552af9e5cc37cae7ef084c68e80a0ac0a4d4c0
5
5
  SHA512:
6
- metadata.gz: 3fbaa085b9054c762040a7a91f19999ce4a7745b388c431ff754f7b7c23ffc6ac21e15f8bc3cfeb995dd8803682cd363117f13577ccd4c7ddb24647f96a179db
7
- data.tar.gz: 336dfa7ce8ee44a663fe2a8678c67966f021f4a2927fded0d239333877e9043e9fead2c503f1296d2df54b225e913cfbfed669d43ad612e3a9921679a710c346
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/server/configuration'
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
 
@@ -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
- info { "#{name} took #{(seconds * 1000).round}ms" }
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: true, code: :passport_verification_failed }
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 authorization request' do
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
- # meter status: :timeout, scope: scope, passport_id: user.passport_id, timeout_ms: human_readable_timeout_in_ms
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! then 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
- # meter status: :valid, passport_id: user.passport_id
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
- # meter status: :valid, passport_id: user.passport_id
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
- # meter status: :invalid, passport_id: user.passport_id
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 (#{response.code.inspect} instead of 200)." }
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
- # This will be a hook for e.g. statistics, benchmarking, etc, measure everything
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(developmen test).include?(environment)
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: true, code: :passport_invalid }
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: true, code: code }.to_json]]
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 creation request for access token #{token.inspect}" }
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 json_code :access_token_not_found unless access_token
26
- return json_code :access_token_invalid unless access_token.valid?
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 json_code :passport_not_found
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 json_code(code)
48
- [200, { 'Content-Type' => 'application/json' }, [{ success: true, code: code }.to_json]]
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
@@ -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
- fail authentication.code
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 :not_json, only: [:new]
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 not_json
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":true,"code":"passport_verification_failed"}']
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.2
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