roo_on_rails 1.14.0 → 1.15.0

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
- SHA256:
3
- metadata.gz: 101bbe2f7ca6f86ba6dc9813c5871be5e18933f2fe75634763eec5b1cfe5adcc
4
- data.tar.gz: 2efc21d30c33572d62183d2f80456c046442b9f2cce8d74d6120fe6e01e029c2
2
+ SHA1:
3
+ metadata.gz: 350514c8b5824a6829103a912a01e155aa18074f
4
+ data.tar.gz: 827cb9bec23c0d69bab1e2df74024e63861853ce
5
5
  SHA512:
6
- metadata.gz: 625ef2e2f32eadc97628075838a991e5271728b1ca282c63cd40b53da344e2bf3e7cd451cd10ec7de8a3025c1e0509eacf5ef498b9e9d7580091b4528b83306c
7
- data.tar.gz: 745e97920deb29280dafe9cc4ed9a72180547823a93f4ea4debbd582a64faa58188b515112443f480087b7a566355bf3bd21c2217ab5082bf3894cc42dc1fd25
6
+ metadata.gz: 5954c887ece5accaeaf6b9acb328ef24978f6d5673ae2ba2ae2cb8d29f0bcc83f1b55346fb500371cba87ad3e8ebeeba2f44d6b0503c96ae7c696a594e03e768
7
+ data.tar.gz: 38aaf68d5cf8271b15597ba0145088f272ceb52cdbeb03602b38fb215dbf4b2220cb6d95008676bfad9062c2a4e831994b6bc1837d450fdc9366819943a701fe
data/CHANGELOG.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # HEAD
2
2
 
3
- _A description of the awesome feature or bug fix you just wrote!_
3
+ _A description of your awesome new stuff here!_
4
+
5
+ # v1.15.0
6
+
7
+ Features:
8
+
9
+ - Process JWTs in `Authorization` headers and populate the request env's `roo.identity` key with the claims, if present and valid. (#79)
10
+ - RooOnRails::Logger is now compatible with ActiveSupport::Logger on Rails versions >= 4.2 (#77)
11
+
12
+ # v1.14.0
13
+
14
+ Bug Fix:
15
+
16
+ - Routemaster Publisher was sending epoch seconds instead of milliseconds (#78)
4
17
 
5
18
  # v1.13.1 (2017-10-18)
6
19
 
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,11 @@
1
+ # Contributing
2
+
3
+ PRs are welcome to this repo to add new features that will be useful across our Ruby services.
4
+
5
+ If you're putting a PR together, please ensure that:
6
+
7
+ - There are ample **tests** for your code (they'll be executed against all our supported versions automatically)
8
+ - You've made changes to the **`README.md`** file explaining how to make use of the work you're adding
9
+ - You've added a line to the **`CHANGELOG.md`** file, with a short explanation of what you've added (feel free to add this in a commit after you've created your PR so you can add the PR number)
10
+
11
+ Thanks!
data/README.md CHANGED
@@ -23,6 +23,7 @@
23
23
  - [Sidekiq](#sidekiq)
24
24
  - [HireFire](#hirefire)
25
25
  - [Logging](#logging)
26
+ - [Identity](#identity)
26
27
  - [Google OAuth authentication](#google-oauth-authentication)
27
28
  - [Datadog Integration](#datadog-integration)
28
29
  - [Routemaster Client](#routemaster-client)
@@ -216,6 +217,31 @@ logger.with(a: 1, b: 2).info('Stuff')
216
217
  See the [class documentation](lib/roo_on_rails/logger.rb) for further
217
218
  details.
218
219
 
220
+ ### Identity
221
+
222
+ If your service wants to accept JWTs for identity claims, then adding the `json-jwt` gem
223
+ to your `Gemfile` and the following railtie to your app will ensure any data presented is
224
+ available:
225
+
226
+ ```ruby
227
+ require 'roo_on_rails/railties/roo_identity'
228
+ ```
229
+
230
+ Any inbound request which has a valid JWT will have the claims made available:
231
+
232
+ ```ruby
233
+ class MyController
234
+ def index
235
+ customer_id = request.env['roo.identity']['cust']
236
+ request.env['roo.identity'].class
237
+ # => JSON::JWT
238
+ end
239
+ end
240
+ ```
241
+
242
+ Be aware that maliciously crafted JWTs will raise 401s that your other middleware can present
243
+ and poorly configured JWT set up will raise errors that you'll be able to catch in test.
244
+
219
245
  ### Google OAuth authentication
220
246
 
221
247
  When `GOOGLE_AUTH_ENABLED` is set to true we inject a `Omniauth` Rack middleware
@@ -1,6 +1,12 @@
1
- require 'logger'
2
1
  require 'delegate'
3
2
  require 'roo_on_rails/logfmt'
3
+ require 'rails/version'
4
+
5
+ if Rails::VERSION::MAJOR < 4
6
+ require 'logger'
7
+ else
8
+ require 'active_support/logger'
9
+ end
4
10
 
5
11
  module RooOnRails
6
12
  # A compatible replacement for the standard Logger to provide context, similar
@@ -30,7 +36,7 @@ module RooOnRails
30
36
  class Logger < SimpleDelegator
31
37
  def initialize(io = STDOUT)
32
38
  @show_timestamp = io.tty?
33
- logger = ::Logger.new(io).tap do |l|
39
+ logger = _default_logger_class.new(io).tap do |l|
34
40
  l.formatter = method(:_formatter)
35
41
  end
36
42
  super(logger)
@@ -100,5 +106,13 @@ module RooOnRails
100
106
  thread_key = @_context_stack_key ||= "roo_on_rails:logging_context:#{object_id}".freeze
101
107
  Thread.current[thread_key] ||= [{}]
102
108
  end
109
+
110
+ def _default_logger_class
111
+ if Rails::VERSION::MAJOR < 4
112
+ ::Logger
113
+ else
114
+ ActiveSupport::Logger
115
+ end
116
+ end
103
117
  end
104
118
  end
@@ -0,0 +1,101 @@
1
+ require 'json/jwt'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+
5
+ module RooOnRails
6
+ module Rack
7
+ class PopulateEnvFromJWT
8
+ UnacceptableKeyError = Class.new(RuntimeError)
9
+ # Hardcoded URLs for valid keys per environment. These will change very infrequently.
10
+ VALID_JWK_URL_PREFIXES = YAML.load(
11
+ File.read(File.expand_path('../valid_identity_service_prefixes.yml', __FILE__))
12
+ ).freeze
13
+
14
+ def initialize(app, logger:, skip_sig_verify: true)
15
+ @app = app
16
+ @keys = {}
17
+ @logger = logger
18
+
19
+ if skip_sig_verify && development?
20
+ @logger.warn "JWTs signature verifification has been switched off in development."
21
+ @verify_sigs = false
22
+ else
23
+ @verify_sigs = true
24
+ end
25
+ end
26
+
27
+ def call(env)
28
+ env['roo.identity'] = decode_authorization_header(env['HTTP_AUTHORIZATION'])
29
+ @app.call(env)
30
+
31
+ # Other exceptions will bubble up, allowing the higher middleware to return a 500, which is
32
+ # intentional.
33
+ rescue UnacceptableKeyError, JSON::JWT::Exception => e
34
+ # Identifying user is clearly attempting to hack or has been given a totally incorrect
35
+ # token, log this and flag as Forbidden, without executing the rest of the middleware stack.
36
+ ::NewRelic::Agent.notice_error(e) if defined?(NewRelic)
37
+ [401, {}, []]
38
+ end
39
+
40
+ private
41
+
42
+ def development?
43
+ if ENV['RACK_ENV'].nil?
44
+ @logger.warn "Your RACK_ENV isn't set. You probably want it set to 'development' in dev."
45
+ end
46
+
47
+ ENV['RACK_ENV'] == 'development'
48
+ end
49
+
50
+ # @raise [UnacceptableKeyError,Faraday::Error,OpenSSL::OpenSSLError] From `#public_key`
51
+ # @raise [JSON::JWT::Exception] Bubble ups from `JSON::JWT.decode`
52
+ # @return [JSON::JWT] The list of claims this header makes by way of a JWS token. Will be an
53
+ # empty hash for invalid or absent tokens.
54
+ def decode_authorization_header(header_value)
55
+ return JSON::JWT.new unless (header_value || '').starts_with?('Bearer ')
56
+ jws_token = header_value[7..-1]
57
+
58
+ JSON::JWT.decode(jws_token, :skip_verification).tap do |jwt|
59
+ jwt.verify!(public_key(jwt.header[:jku])) if @verify_sigs
60
+ end
61
+ end
62
+
63
+ def acceptable_key?(key_url)
64
+ return false if key_url.nil?
65
+ @key_prefixes ||= VALID_JWK_URL_PREFIXES[ENV['RACK_ENV']]
66
+ @key_prefixes.any? { |acceptable| key_url.starts_with?(acceptable) }
67
+ end
68
+
69
+ # @raise [UnacceptableKeyError] When the key URL is not from a trusted location
70
+ # @raise [Faraday::Error] When the JWK at the given URL is not retrievable for some reason.
71
+ # See: https://github.com/lostisland/faraday/blob/master/lib/faraday/error.rb
72
+ # @return [JSON::JWK] The JWK for the specified URL
73
+ def public_key(key_url)
74
+ unless acceptable_key?(key_url)
75
+ raise UnacceptableKeyError, "#{key_url} is not a valid Deliveroo Key URL"
76
+ end
77
+
78
+ # NB. don't use ||= memoization, or this middleware can be attacked by
79
+ # being asked to decode large numbers of non-existant key-ids, each of
80
+ # which would fill the @keys hash with a tuple.
81
+ return @keys[key_url] if @keys.key?(key_url)
82
+
83
+ @logger.info "Downloading identity public key from #{key_url}"
84
+ json = http_request.get(key_url).body
85
+ @keys[key_url] = JSON::JWK.new(json)
86
+ rescue Faraday::ParsingError
87
+ raise JSON::JWT::InvalidFormat, 'Downloaded JWK is not a valid JSON file'
88
+ end
89
+
90
+ def http_request
91
+ Faraday.new do |conf|
92
+ conf.response :json
93
+ conf.response :raise_error
94
+ conf.request :json
95
+
96
+ conf.adapter Faraday.default_adapter
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,6 @@
1
+ production:
2
+ - https://deliveroo.co.uk/identity-keys/
3
+ - https://identity.deliveroo.net/identity/keys/
4
+ staging:
5
+ - https://test.deliveroo.co.uk/identity-keys/
6
+ - https://identity-staging.deliveroo.net/identity-keys/
@@ -0,0 +1,24 @@
1
+ require 'roo_on_rails/config'
2
+
3
+ module RooOnRails
4
+ module Railties
5
+ class RooIdentity < Rails::Railtie
6
+ initializer 'roo_on_rails.roo_identity.middleware' do |app|
7
+ Rails.logger.with initializer: 'roo_on_rails.roo_identity' do |log|
8
+ log.debug 'loading'
9
+ _add_middleware(app, log)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ def _add_middleware(app, log)
16
+ require 'roo_on_rails/rack/populate_env_from_jwt'
17
+
18
+ app.config.middleware.use RooOnRails::Rack::PopulateEnvFromJWT, logger: log
19
+ rescue LoadError
20
+ log.error 'the json-jwt gem is not in the bundle so Roo Identity will not be available'
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module RooOnRails
2
- VERSION = '1.14.0'.freeze
2
+ VERSION = '1.15.0'.freeze
3
3
  end
data/roo_on_rails.gemspec CHANGED
@@ -37,6 +37,9 @@ Gem::Specification.new do |spec|
37
37
  spec.add_runtime_dependency 'faraday_middleware'
38
38
  spec.add_runtime_dependency 'routemaster-client'
39
39
 
40
+ # Optional gems you may add to your project
41
+ spec.add_development_dependency 'json-jwt', '~> 1.8'
42
+
40
43
  spec.add_development_dependency 'bundler', '~> 1.13'
41
44
  spec.add_development_dependency 'rake', '~> 10.0'
42
45
  spec.add_development_dependency 'rspec', '~> 3.0'
@@ -46,4 +49,5 @@ Gem::Specification.new do |spec|
46
49
  spec.add_development_dependency 'simplecov'
47
50
  spec.add_development_dependency 'codecov'
48
51
  spec.add_development_dependency 'rack-test'
52
+ spec.add_development_dependency 'rspec-its'
49
53
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roo_on_rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.0
4
+ version: 1.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Julien Letessier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-11-02 00:00:00.000000000 Z
11
+ date: 2017-11-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv-rails
@@ -226,6 +226,20 @@ dependencies:
226
226
  - - ">="
227
227
  - !ruby/object:Gem::Version
228
228
  version: '0'
229
+ - !ruby/object:Gem::Dependency
230
+ name: json-jwt
231
+ requirement: !ruby/object:Gem::Requirement
232
+ requirements:
233
+ - - "~>"
234
+ - !ruby/object:Gem::Version
235
+ version: '1.8'
236
+ type: :development
237
+ prerelease: false
238
+ version_requirements: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - "~>"
241
+ - !ruby/object:Gem::Version
242
+ version: '1.8'
229
243
  - !ruby/object:Gem::Dependency
230
244
  name: bundler
231
245
  requirement: !ruby/object:Gem::Requirement
@@ -352,6 +366,20 @@ dependencies:
352
366
  - - ">="
353
367
  - !ruby/object:Gem::Version
354
368
  version: '0'
369
+ - !ruby/object:Gem::Dependency
370
+ name: rspec-its
371
+ requirement: !ruby/object:Gem::Requirement
372
+ requirements:
373
+ - - ">="
374
+ - !ruby/object:Gem::Version
375
+ version: '0'
376
+ type: :development
377
+ prerelease: false
378
+ version_requirements: !ruby/object:Gem::Requirement
379
+ requirements:
380
+ - - ">="
381
+ - !ruby/object:Gem::Version
382
+ version: '0'
355
383
  description: Scaffolding for building services
356
384
  email:
357
385
  - julien.letessier@gmail.com
@@ -371,6 +399,7 @@ files:
371
399
  - ".travis.yml"
372
400
  - Appraisals
373
401
  - CHANGELOG.md
402
+ - CONTRIBUTING.md
374
403
  - Gemfile
375
404
  - Guardfile
376
405
  - LICENSE.txt
@@ -424,7 +453,9 @@ files:
424
453
  - lib/roo_on_rails/logfmt.rb
425
454
  - lib/roo_on_rails/logger.rb
426
455
  - lib/roo_on_rails/papertrail_client.rb
456
+ - lib/roo_on_rails/rack/populate_env_from_jwt.rb
427
457
  - lib/roo_on_rails/rack/safe_timeouts.rb
458
+ - lib/roo_on_rails/rack/valid_identity_service_prefixes.yml
428
459
  - lib/roo_on_rails/railties/database.rb
429
460
  - lib/roo_on_rails/railties/env.rb
430
461
  - lib/roo_on_rails/railties/google_oauth.rb
@@ -432,6 +463,7 @@ files:
432
463
  - lib/roo_on_rails/railties/logging.rb
433
464
  - lib/roo_on_rails/railties/new_relic.rb
434
465
  - lib/roo_on_rails/railties/rake_tasks.rb
466
+ - lib/roo_on_rails/railties/roo_identity.rb
435
467
  - lib/roo_on_rails/railties/routemaster.rb
436
468
  - lib/roo_on_rails/railties/sidekiq.rb
437
469
  - lib/roo_on_rails/routemaster/lifecycle_events.rb
@@ -470,7 +502,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
470
502
  version: '0'
471
503
  requirements: []
472
504
  rubyforge_project:
473
- rubygems_version: 2.7.0
505
+ rubygems_version: 2.6.11
474
506
  signing_key:
475
507
  specification_version: 4
476
508
  summary: Scaffolding for building services