roo_on_rails 1.11.1 → 1.12.0

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: 71e9f8c88b0d2cc851905371ded727515b81655f
4
- data.tar.gz: 0ca3d032fd13e53e14888833024b50bf068348f4
3
+ metadata.gz: 05edf07734c3e18175c4b7f10d220b4443e8b23f
4
+ data.tar.gz: fd09c771c91cfb18b2c1cc31ec0566f4585eee19
5
5
  SHA512:
6
- metadata.gz: 82ec9396a7be546d406db40f8eebb252569a40c87824cd8394312d9cbff625406f81401a4e85b9cc8fa619732bda1452c613174e72cf410dffc38dd07d4cd6f3
7
- data.tar.gz: 2dd9ddcd748498df776be201a5a2b34345580119ca2a0dc6728150c67152c0c3a678d7b7710c50f895de058eb6536a6134cab5b07471485d41a0161b8583fcfb
6
+ metadata.gz: 9bbcfae06c1983dddf2bf86a037207bd93dc59ec86ba9772320a8e90e1d16bf6b034b848cf23292f755eb6e50557a99a0c6952d3099d5070386838db3904e404
7
+ data.tar.gz: d7c935d825c03932d8cea692b4f9fefd8b971433283b4e4fe480143d3e4f043ae5260c4240a29369add01af2e62110d5fbef0062400dcca4b5b3daf88f366e14
data/CHANGELOG.md CHANGED
@@ -1,3 +1,8 @@
1
+ # v1.12.0 (2017-09-27)
2
+
3
+ Features (library):
4
+ - Provides API authentication concern for controllers. (#67)
5
+
1
6
  # v1.11.1 (2017-09-11)
2
7
 
3
8
  Bug fixes:
data/README.md CHANGED
@@ -256,6 +256,34 @@ When `ROUTEMASTER_ENABLED` is set to `true` we attempt to configure [`routemaste
256
256
 
257
257
  If you then want to enable the publishing of events onto the event bus, you need to set `ROUTEMASTER_PUBLISHING_ENABLED` to `true` and implement publishers as needed. An example of how to do this is detailed in [`README.routemaster_client.md`](README.routemaster_client.md).
258
258
 
259
+ ### API Authentication
260
+
261
+ RooOnRails provides a concern which will make adding rotatable API authentication to your service a breeze:
262
+
263
+ ```ruby
264
+ require 'roo_on_rails/concerns/require_api_key'
265
+
266
+ class ThingController < ActionController::Base
267
+ require_api_key
268
+ # or
269
+ require_api_key(only: :update)
270
+ # or
271
+ require_api_key(only_services: %i(service_1 service_2))
272
+
273
+ def index
274
+ # etc
275
+ end
276
+ ```
277
+
278
+ Keys are specified in environment variables ending with `_CLIENT_KEY`, where the value is a comma separated list of keys which the specified service can authenticate with. This means that if your service has the environment variables:
279
+
280
+ ```
281
+ SERVICE_1_CLIENT_KEY=abc123abc123,def456def456
282
+ SERVICE_2_CLIENT_KEY=I-never-could-get-the-hang-of-Thursdays
283
+ ```
284
+
285
+ Then, for any controller where this concern has been initiated, Basic Authentication will be required and only `service_1:abc123abc123`, `service_1:def456def456` and `service_2:I-never-could-get-the-hang-of-Thursdays` will be allowed access.
286
+
259
287
  ## Command features
260
288
 
261
289
  ### Usage
@@ -0,0 +1,108 @@
1
+ require 'active_support/concern'
2
+ require 'action_controller/metal/http_authentication'
3
+
4
+ module RooOnRails
5
+ module Concerns
6
+ # This concern allows API authentication in a consistent manner.
7
+ #
8
+ # If a service connects with basic auth using the username "service" then the
9
+ # `SERVICE_CLIENT_KEY` environment variable must have the given password as one of
10
+ # the comma separated strings within it or a 403 will be raised.
11
+ #
12
+ # @example: Any service with an acceptable key can access routes
13
+ #
14
+ # class ThingController < ApplicationController
15
+ # include RooOnRails::Concerns::RequireApiKey
16
+ # require_api_key
17
+ #
18
+ # # etc
19
+ # end
20
+ #
21
+ # @example: Only the specified clients can access specific routes in this controller
22
+ #
23
+ # class ThingController < ApplicationController
24
+ # include RooOnRails::Concerns::RequireApiKey
25
+ # require_api_key(only_services: :my_service, only: :create)
26
+ #
27
+ # # etc
28
+ # end
29
+ module RequireApiKey
30
+ extend ActiveSupport::Concern
31
+ include ActionController::HttpAuthentication::Basic::ControllerMethods
32
+
33
+ attr_reader :current_client
34
+
35
+ module ClassMethods
36
+ # Declares that routes on the controller must have access credentials specified
37
+ # in the request that match the approparite environment variables.
38
+ #
39
+ # @param :only_services (#to_s,Array<#to_s>) Restricts the services which will be accepted
40
+ # @see AbstractController::Callbacks::ClassMethods#before_action for additional scoping opts
41
+ def require_api_key(only_services: nil, **options)
42
+ before_action(**options) do
43
+ authenticate_or_request_with_http_basic('Authenitcation required') do |service_name, client_key|
44
+ Authenticator.new([*only_services]).valid?(service_name, client_key).tap do |is_valid|
45
+ @current_client = OpenStruct.new(name: service_name).freeze if is_valid
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ # This functionality pulled out into a new class for testability
53
+ class Authenticator
54
+ def initialize(whitelisted_clients)
55
+ @whitelisted_clients = whitelisted_clients.map(&:to_s)
56
+ end
57
+
58
+ def valid?(service_name, client_key)
59
+ return false unless whitelisted?(service_name)
60
+
61
+ NewRelic::Agent.add_custom_attributes(httpBasicUserId: service_name) if defined?(NewRelic)
62
+ ClientApiKeys.instance.valid?(service_name, client_key)
63
+ end
64
+
65
+ private
66
+
67
+ def whitelisted?(service_name)
68
+ return true if @whitelisted_clients.empty?
69
+ @whitelisted_clients.include?(service_name)
70
+ end
71
+ end
72
+
73
+ class ClientApiKeys
74
+ include Singleton
75
+
76
+ CLIENT_KEY_NAME_SUFFIX_REGEX = /_CLIENT_KEY\Z/
77
+
78
+ def initialize
79
+ @cache = ENV.select { |key| key =~ CLIENT_KEY_NAME_SUFFIX_REGEX }
80
+ .map { |k, v| [service_name(k), parse_client_keys(v)] }
81
+ .to_h
82
+ .freeze
83
+ end
84
+
85
+ def valid?(service_name, client_key)
86
+ return false if service_name == '' || client_key == ''
87
+
88
+ client_keys = @cache[normalize(service_name)]
89
+ client_keys && client_keys.include?(client_key)
90
+ end
91
+
92
+ private
93
+
94
+ def service_name(client_key_name)
95
+ normalize(client_key_name.sub(CLIENT_KEY_NAME_SUFFIX_REGEX, ''))
96
+ end
97
+
98
+ def normalize(service_name)
99
+ service_name.upcase.gsub(/[A-Z0-9]+/, '_')
100
+ end
101
+
102
+ def parse_client_keys(str)
103
+ (str || '').split(',').map(&:strip).to_set.freeze
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -1,3 +1,3 @@
1
1
  module RooOnRails
2
- VERSION = '1.11.1'.freeze
2
+ VERSION = '1.12.0'.freeze
3
3
  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.11.1
4
+ version: 1.12.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-09-11 00:00:00.000000000 Z
11
+ date: 2017-09-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv-rails
@@ -415,6 +415,7 @@ files:
415
415
  - lib/roo_on_rails/checks/papertrail/token.rb
416
416
  - lib/roo_on_rails/checks/sidekiq/settings.rb
417
417
  - lib/roo_on_rails/checks/sidekiq/sidekiq.rb
418
+ - lib/roo_on_rails/concerns/require_api_key.rb
418
419
  - lib/roo_on_rails/config.rb
419
420
  - lib/roo_on_rails/context_logging.rb
420
421
  - lib/roo_on_rails/default.env