sso 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3427c7a74fc824e4a590aae4dedb206cd0c3f89c
4
- data.tar.gz: 13355ce7e47c5088774fc5608b453dee91578136
3
+ metadata.gz: 5e1f2feee69f64105e38a748befeebe0de6d33a5
4
+ data.tar.gz: c0637201a0d104c075dc7c049eef0d9f381e0596
5
5
  SHA512:
6
- metadata.gz: 976e470dbb0a78ac0184a6a5d4381af04008cf158516d4b9106b08ad3eb2865257e6da196ba4affdd4e2318e50bb1f4aeed67e5b8fd6c47c4b2a8bfe1c96b5ff
7
- data.tar.gz: c4cf08a461736d1df4ff3ff88a69fa6c424354cfe1739d6e561839b3f7f9798484cc75b86e20e48f0d7556441e4bb71757c033efd0deb0dbafcdb7868392fd18
6
+ metadata.gz: 3fbaa085b9054c762040a7a91f19999ce4a7745b388c431ff754f7b7c23ffc6ac21e15f8bc3cfeb995dd8803682cd363117f13577ccd4c7ddb24647f96a179db
7
+ data.tar.gz: 336dfa7ce8ee44a663fe2a8678c67966f021f4a2927fded0d239333877e9043e9fead2c503f1296d2df54b225e913cfbfed669d43ad612e3a9921679a710c346
@@ -127,7 +127,7 @@ module SSO
127
127
  decipher.key = chip_key
128
128
  decipher.iv = chip_iv
129
129
  plaintext = decipher.update(chip_ciphertext) + decipher.final
130
- logger.debug { "Decryptied chip plaintext #{plaintext.inspect} using key #{chip_key.inspect} and iv #{chip_iv.inspect} and ciphertext #{chip_ciphertext.inspect}" }
130
+ logger.debug { "Decrypted chip plaintext #{plaintext.inspect} using key #{chip_key.inspect} and iv #{chip_iv.inspect} and ciphertext #{chip_ciphertext.inspect}" }
131
131
  plaintext
132
132
  end
133
133
  end
@@ -52,7 +52,7 @@ module SSO
52
52
  def received_passport
53
53
  ::SSO::Client::Passport.new received_passport_attributes
54
54
 
55
- rescue ArgumentError => exception
55
+ rescue ArgumentError
56
56
  error { "Could not instantiate Passport from serialized response #{received_passport_attributes.inspect}" }
57
57
  raise
58
58
  end
@@ -66,7 +66,25 @@ module SSO
66
66
  end
67
67
 
68
68
  def params
69
- { ip: user_ip, agent: user_agent, device_id: device_id, state: passport_state }
69
+ result = { ip: user_ip, agent: user_agent, device_id: device_id, state: passport_state }
70
+ result.merge! insider_id: insider_id, insider_signature: insider_signature
71
+ result
72
+ end
73
+
74
+ def insider_id
75
+ ::SSO.config.oauth_client_id
76
+ end
77
+
78
+ def insider_secret
79
+ ::SSO.config.oauth_client_secret
80
+ end
81
+
82
+ def insider_signature
83
+ ::OpenSSL::HMAC.hexdigest signature_digest, insider_secret, user_ip
84
+ end
85
+
86
+ def signature_digest
87
+ OpenSSL::Digest.new 'sha1'
70
88
  end
71
89
 
72
90
  def token
@@ -89,7 +107,7 @@ module SSO
89
107
  (timeout_in_milliseconds / 1000).round 2
90
108
  end
91
109
 
92
- # TODO Needs to be configurable
110
+ # TODO: Needs to be configurable
93
111
  def path
94
112
  OmniAuth::Strategies::SSO.passports_path
95
113
  end
@@ -109,7 +109,7 @@ module SSO
109
109
  # This will be a hook for e.g. statistics, benchmarking, etc, measure everything
110
110
  end
111
111
 
112
- # TODO Use ActionDispatch remote IP or you might get the Load Balancer's IP instead :(
112
+ # TODO: Use ActionDispatch remote IP or you might get the Load Balancer's IP instead :(
113
113
  def ip
114
114
  request.ip
115
115
  end
@@ -10,6 +10,7 @@ require 'sso'
10
10
  require 'sso/server/errors'
11
11
  require 'sso/server/passport'
12
12
  require 'sso/server/passports'
13
+ require 'sso/server/passports/activity'
13
14
  require 'sso/server/engine'
14
15
 
15
16
  require 'sso/server/authentications/passport'
@@ -54,7 +54,6 @@ module SSO
54
54
  yield Operations.failure :passport_not_found if passport.blank?
55
55
  yield Operations.failure :passport_revoked if passport.invalid?
56
56
  debug { 'Arguments are fine.' }
57
- #yield Operations.failure :user_not_encapsulated if passport.user.blank?
58
57
  Operations.success :arguments_are_valid
59
58
  end
60
59
 
@@ -83,7 +82,8 @@ module SSO
83
82
  end
84
83
 
85
84
  def passport!
86
- return unless record = backend.find_by_id(passport_id)
85
+ record = backend.find_by_id(passport_id)
86
+ return unless record
87
87
  debug { "Successfully loaded Passport #{passport_id} from database." }
88
88
  record
89
89
  end
@@ -37,6 +37,16 @@ module SSO
37
37
  end
38
38
  attr_writer :passport_verification_timeout_ms
39
39
 
40
+ def oauth_client_id
41
+ @oauth_client_id || fail('You need to configure the oauth_client_id, see SSO::Configuration for more info.')
42
+ end
43
+ attr_writer :oauth_client_id
44
+
45
+ def oauth_client_secret
46
+ @oauth_client_secret || fail('You need to configure the oauth_client_secret, see SSO::Configuration for more info.')
47
+ end
48
+ attr_writer :oauth_client_secret
49
+
40
50
  # Both
41
51
 
42
52
  def exception_handler
@@ -89,13 +99,13 @@ module SSO
89
99
  proc do |exception|
90
100
  return unless ::SSO.config.logger
91
101
  ::SSO.config.logger.error(self.class) do
92
- "An internal error occured #{exception.class.name} #{exception.message} #{exception.backtrace[0..5].join(' ') if exception.backtrace}"
102
+ "An internal error occurred #{exception.class.name} #{exception.message} #{exception.backtrace[0..5].join(' ') if exception.backtrace}"
93
103
  end
94
104
  end
95
105
  end
96
106
 
97
107
  def default_human_readable_location_for_ip
98
- proc do |ip|
108
+ proc do
99
109
  'Unknown'
100
110
  end
101
111
  end
@@ -11,7 +11,7 @@ module SSO
11
11
  def call(env)
12
12
  request = Rack::Request.new(env)
13
13
 
14
- if !(request.delete? && request.path.start_with?(passports_path))
14
+ unless request.delete? && request.path.start_with?(passports_path)
15
15
  debug { "I'm not interested in this #{request.request_method.inspect} request to #{request.path.inspect} I only care for DELETE #{passports_path.inspect}" }
16
16
  return @app.call(env)
17
17
  end
@@ -12,10 +12,8 @@ module SSO
12
12
 
13
13
  def call(env)
14
14
  request = Rack::Request.new(env)
15
- remote_ip = request.env['action_dispatch.remote_ip'].to_s
16
- device_id = request.params['device_id']
17
15
 
18
- if !(request.post? && request.path == passports_path)
16
+ unless request.post? && request.path == passports_path
19
17
  debug { "I'm not interested in this #{request.request_method.inspect} request to #{request.path.inspect} I only care for POST #{passports_path.inspect}" }
20
18
  return @app.call(env)
21
19
  end
@@ -24,13 +22,8 @@ module SSO
24
22
  debug { "Detected incoming Passport creation request for access token #{token.inspect}" }
25
23
  access_token = ::Doorkeeper::AccessToken.find_by_token token
26
24
 
27
- unless access_token
28
- return json_code :access_token_not_found
29
- end
30
-
31
- unless access_token.valid?
32
- return json_code :access_token_invalid
33
- end
25
+ return json_code :access_token_not_found unless access_token
26
+ return json_code :access_token_invalid unless access_token.valid?
34
27
 
35
28
  finding = ::SSO::Server::Passports.find_by_access_token_id(access_token.id)
36
29
  if finding.failure?
@@ -71,7 +71,7 @@ module SSO
71
71
  ciphertext = cipher.update chip_plaintext
72
72
  ciphertext << cipher.final
73
73
  debug { "The Passport chip plaintext #{chip_plaintext.inspect} was encrypted using key #{chip_key.inspect} and IV #{chip_iv.inspect} and resultet in ciphertext #{ciphertext.inspect}" }
74
- chip = [Base64.encode64(ciphertext).strip(), Base64.encode64(chip_iv).strip()].join('|')
74
+ chip = [Base64.encode64(ciphertext).strip, Base64.encode64(chip_iv).strip].join('|')
75
75
  logger.debug { "Augmented passport #{id.inspect} with chip #{chip.inspect}" }
76
76
  chip
77
77
  end
@@ -43,28 +43,7 @@ module SSO
43
43
  def self.update_activity(passport_id:, request:)
44
44
  record = find_valid_passport(passport_id) { |failure| return failure }
45
45
 
46
- immediate_ip = request.respond_to?(:remote_ip) ? request.remote_ip : request.ip
47
- if record.insider?
48
- proxied_ip = request['ip']
49
- unless proxied_ip
50
- warn { "There should have been a proxied IP param, but there was none. I will use the immediare IP #{immediate_ip} now." }
51
- proxied_ip = immediate_ip
52
- end
53
- attributes = { ip: proxied_ip, agent: request['agent'], device: request['device_id'] }
54
- else
55
- attributes = { ip: immediate_ip, agent: request.user_agent, device: request.params['device_id'] }
56
- end
57
- attributes.merge! activity_at: Time.now
58
-
59
- record.stamps ||= {} # <- Not thread-safe, this may potentially delete all existing stamps, I guess
60
- record.stamps[attributes[:ip]] = Time.now.to_i
61
-
62
- debug { "Updating activity of #{record.insider? ? :insider : :outsider} Passport #{passport_id.inspect} using IP #{attributes[:ip]} agent #{attributes[:agent]} and device #{attributes[:device]}" }
63
- if record.update_attributes(attributes)
64
- Operations.success :passport_metadata_updated
65
- else
66
- Operations.failure :could_not_update_passport_activity, object: record.errors.to_hash
67
- end
46
+ ::SSO::Server::Passports::Activity.new(passport: record, request: request).call
68
47
  end
69
48
 
70
49
  def self.register_authorization_grant(passport_id:, token:)
@@ -0,0 +1,92 @@
1
+ module SSO
2
+ module Server
3
+ module Passports
4
+ class Activity
5
+ include ::SSO::Logging
6
+
7
+ attr_reader :passport, :request
8
+
9
+ def initialize(passport:, request:)
10
+ @passport = passport
11
+ @request = request
12
+ end
13
+
14
+ def call
15
+ if passport.insider? || trusted_proxy_app?
16
+ proxied_ip = request['ip']
17
+ unless proxied_ip
18
+ warn { "There should have been a proxied IP param, but there was none. I will use the immediate IP #{immediate_ip} now." }
19
+ proxied_ip = immediate_ip
20
+ end
21
+ attributes = { ip: proxied_ip, agent: request['agent'], device: request['device_id'] }
22
+ else
23
+ attributes = { ip: immediate_ip, agent: request.user_agent, device: request['device_id'] }
24
+ end
25
+ attributes.merge! activity_at: Time.now
26
+
27
+ passport.stamps ||= {} # <- Not thread-safe, this may potentially delete all existing stamps, I guess
28
+ passport.stamps[attributes[:ip]] = Time.now.to_i
29
+
30
+ debug { "Updating activity of #{passport.insider? ? :insider : :outsider} Passport #{passport.id.inspect} using IP #{attributes[:ip]} agent #{attributes[:agent]} and device #{attributes[:device]}" }
31
+ if passport.update_attributes(attributes)
32
+ Operations.success :passport_metadata_updated
33
+ else
34
+ Operations.failure :could_not_update_passport_activity, object: passport.errors.to_hash
35
+ end
36
+ end
37
+
38
+ def trusted_proxy_app?
39
+ unless insider_id
40
+ debug { 'This is an immediate request because there is no insider_id param' }
41
+ return
42
+ end
43
+
44
+ unless insider_signature
45
+ debug { 'This is an immediate request because there is no insider_signature param' }
46
+ return
47
+ end
48
+
49
+ application = ::Doorkeeper::Application.find_by_id(insider_id)
50
+ unless application
51
+ warn { 'The insider_id param does not correspond to an existing Doorkeeper Application' }
52
+ return
53
+ end
54
+
55
+ unless application.scopes.include?('insider')
56
+ warn { 'The Doorkeeper Application belonging to this insider_id param is considered an outsider' }
57
+ return
58
+ end
59
+
60
+ expected_signature = ::OpenSSL::HMAC.hexdigest signature_digest, application.secret, proxied_ip
61
+ unless insider_signature == expected_signature
62
+ warn { "The insider signature #{insider_signature.inspect} does not match my expectation #{expected_signature.inspect}" }
63
+ return
64
+ end
65
+
66
+ debug { 'This is a proxied request because insider_id and insider_signature are valid' }
67
+ true
68
+ end
69
+
70
+ def signature_digest
71
+ OpenSSL::Digest.new 'sha1'
72
+ end
73
+
74
+ def proxied_ip
75
+ request['ip']
76
+ end
77
+
78
+ def insider_id
79
+ request['insider_id']
80
+ end
81
+
82
+ def insider_signature
83
+ request['insider_signature']
84
+ end
85
+
86
+ def immediate_ip
87
+ request.respond_to?(:remote_ip) ? request.remote_ip : request.ip
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe SSO::Client::Authentications::Passport, type: :request, db: true do
3
+ RSpec.describe SSO::Client::Authentications::Passport, type: :request, db: true, reveal_exceptions: true do
4
4
 
5
5
  # Untrusted Client
6
6
  let(:request_method) { 'GET' }
@@ -31,12 +31,15 @@ RSpec.describe SSO::Client::Authentications::Passport, type: :request, db: true
31
31
  let(:passport_chip) { server_passport.chip! }
32
32
 
33
33
  # Server
34
+ let(:oauth_client) { create :outsider_doorkeeper_application }
34
35
  let(:insider) { false }
35
36
  let(:server_user) { create :user, name: 'Emily', tags: %i(cool nice) }
36
37
  let!(:server_passport) { create :passport, user: server_user, owner_id: server_user.id, ip: ip, agent: agent, insider: insider }
37
38
 
38
39
  before do
39
40
  SSO.config.passport_chip_key = SecureRandom.hex
41
+ SSO.config.oauth_client_id = oauth_client.id
42
+ SSO.config.oauth_client_secret = oauth_client.secret
40
43
  end
41
44
 
42
45
  context 'no changes' do
@@ -57,16 +60,26 @@ RSpec.describe SSO::Client::Authentications::Passport, type: :request, db: true
57
60
  expect(passport).to be_modified
58
61
  end
59
62
 
60
- it 'tracks the immediate request IP' do
61
- expect(server_passport.reload.ip).to eq '127.0.0.1'
62
- end
63
-
64
63
  it 'attaches the user attributes to the passport' do
65
64
  expect(passport.user).to be_instance_of Hash
66
65
  expect(passport.user['name']).to eq 'Emily'
67
66
  expect(passport.user['email']).to eq 'emily@example.com'
68
67
  expect(passport.user['tags'].sort).to eq %w(cool is_working_from_home nice).sort
69
68
  end
69
+
70
+ context 'outsider oauth client' do
71
+ it 'tracks the immediate request IP' do
72
+ expect(server_passport.reload.ip).to eq '127.0.0.1'
73
+ end
74
+ end
75
+
76
+ context 'insider oauth client' do
77
+ let(:oauth_client) { create :insider_doorkeeper_application }
78
+
79
+ it 'tracks the untrusted client IP' do
80
+ expect(server_passport.reload.ip).to eq ip
81
+ end
82
+ end
70
83
  end
71
84
 
72
85
  context 'insider passport' do
@@ -27,6 +27,8 @@ RSpec.describe SSO::Client::Warden::Hooks::AfterFetch, type: :request, db: true
27
27
  # The server dynamically injects some tags. In order to calculate the user state correctly in our test setup,
28
28
  # We need to "simulate" what the tags will look like once the server modified them. No big problem.
29
29
  allow(server_user).to receive(:tags).and_return %w(wears_glasses is_working_from_home never_gives_up)
30
+ SSO.config.oauth_client_id = SecureRandom.hex
31
+ SSO.config.oauth_client_secret = SecureRandom.hex
30
32
  end
31
33
 
32
34
  context 'invalid passport' do
@@ -44,8 +44,16 @@ RSpec.configure do |config|
44
44
  DatabaseCleaner.start
45
45
  end
46
46
 
47
+ config.before :each, reveal_exceptions: true do
48
+ SSO.config.exception_handler = proc { |exception| fail exception }
49
+ end
50
+
47
51
  config.after :each do
48
52
  Timecop.return
53
+ SSO.config.exception_handler = nil
54
+ SSO.config.passport_chip_key = nil
55
+ SSO.config.oauth_client_id = nil
56
+ SSO.config.oauth_client_secret = nil
49
57
  end
50
58
 
51
59
  config.after :each, db: true do
@@ -5,8 +5,8 @@ module SSO
5
5
  module Helpers
6
6
 
7
7
  # Inspired by Warden::Spec::Helpers
8
- def env_with_params(path = "/", params = {}, env = {})
9
- method = params.delete(:method) || "GET"
8
+ def env_with_params(path = '/', params = {}, env = {})
9
+ method = params.delete(:method) || 'GET'
10
10
  env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }.merge(env)
11
11
  Rack::MockRequest.env_for "#{path}?#{Rack::Utils.build_query(params)}", env
12
12
  end
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.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - halo
@@ -281,6 +281,7 @@ files:
281
281
  - lib/sso/server/middleware/passport_verification.rb
282
282
  - lib/sso/server/passport.rb
283
283
  - lib/sso/server/passports.rb
284
+ - lib/sso/server/passports/activity.rb
284
285
  - lib/sso/server/warden/hooks/after_authentication.rb
285
286
  - lib/sso/server/warden/hooks/before_logout.rb
286
287
  - lib/sso/server/warden/strategies/passport.rb