sso 0.1.1 → 0.1.2

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: 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