lhs 21.2.4 → 22.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +264 -116
  3. data/lhs.gemspec +1 -1
  4. data/lib/lhs.rb +8 -0
  5. data/lib/lhs/concerns/o_auth.rb +25 -0
  6. data/lib/lhs/concerns/record/chainable.rb +4 -4
  7. data/lib/lhs/concerns/record/configuration.rb +28 -11
  8. data/lib/lhs/concerns/record/request.rb +26 -7
  9. data/lib/lhs/concerns/record/update.rb +17 -0
  10. data/lib/lhs/config.rb +1 -1
  11. data/lib/lhs/interceptors/auto_oauth/interceptor.rb +33 -0
  12. data/lib/lhs/interceptors/auto_oauth/thread_registry.rb +18 -0
  13. data/lib/lhs/record.rb +6 -3
  14. data/lib/lhs/version.rb +1 -1
  15. data/spec/auto_oauth_spec.rb +169 -0
  16. data/spec/dummy/app/controllers/application_controller.rb +15 -0
  17. data/spec/dummy/app/controllers/automatic_authentication_controller.rb +29 -0
  18. data/spec/dummy/app/models/dummy_record_with_auto_oauth_provider.rb +6 -0
  19. data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers1.rb +7 -0
  20. data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers2.rb +7 -0
  21. data/spec/dummy/app/models/dummy_record_with_multiple_providers_per_endpoint.rb +6 -0
  22. data/spec/dummy/app/models/dummy_record_with_oauth.rb +7 -0
  23. data/spec/dummy/app/models/providers/internal_services.rb +7 -0
  24. data/spec/dummy/config/routes.rb +5 -0
  25. data/spec/item/destroy_spec.rb +1 -1
  26. data/spec/proxy/record_identification_spec.rb +1 -1
  27. data/spec/record/all_spec.rb +1 -1
  28. data/spec/record/endpoints_spec.rb +1 -1
  29. data/spec/record/error_handling_integration_spec.rb +1 -1
  30. data/spec/record/handle_includes_errors_spec.rb +1 -1
  31. data/spec/record/has_many_spec.rb +1 -1
  32. data/spec/record/has_one_spec.rb +1 -1
  33. data/spec/record/includes_first_page_spec.rb +727 -0
  34. data/spec/record/includes_spec.rb +545 -579
  35. data/spec/record/includes_warning_spec.rb +1 -1
  36. data/spec/record/mapping_spec.rb +2 -2
  37. data/spec/record/references_spec.rb +1 -1
  38. data/spec/record/relation_caching_spec.rb +3 -3
  39. data/spec/record/update_spec.rb +62 -0
  40. data/spec/request_cycle_cache_spec.rb +3 -3
  41. metadata +30 -8
  42. data/spec/record/includes_all_spec.rb +0 -693
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
32
32
  s.add_development_dependency 'pry'
33
33
  s.add_development_dependency 'pry-byebug'
34
34
  s.add_development_dependency 'rails', '>= 4.2.11'
35
- s.add_development_dependency 'rollbar'
35
+ s.add_development_dependency 'rollbar', '<= 2.24.0'
36
36
  s.add_development_dependency 'rspec-rails', '>= 3.7.0'
37
37
  s.add_development_dependency 'rubocop', '~> 0.57.1'
38
38
  s.add_development_dependency 'rubocop-rspec', '~> 1.26.0'
data/lib/lhs.rb CHANGED
@@ -22,6 +22,12 @@ module LHS
22
22
  autoload :Inspect,
23
23
  'lhs/concerns/inspect'
24
24
  module Interceptors
25
+ module AutoOauth
26
+ autoload :ThreadRegistry,
27
+ 'lhs/interceptors/auto_oauth/thread_registry'
28
+ autoload :Interceptor,
29
+ 'lhs/interceptors/auto_oauth/interceptor'
30
+ end
25
31
  module RequestCycleCache
26
32
  autoload :ThreadRegistry,
27
33
  'lhs/interceptors/request_cycle_cache/thread_registry'
@@ -41,6 +47,8 @@ module LHS
41
47
  'lhs/concerns/is_href'
42
48
  autoload :Item,
43
49
  'lhs/item'
50
+ autoload :OAuth,
51
+ 'lhs/concerns/o_auth.rb'
44
52
  autoload :OptionBlocks,
45
53
  'lhs/concerns/option_blocks'
46
54
  autoload :Pagination,
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module LHS
6
+ module OAuth
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ prepend_before_action :lhs_store_oauth_access_token
11
+ end
12
+
13
+ private
14
+
15
+ def lhs_store_oauth_access_token
16
+ lhs_check_auto_oauth_enabled!
17
+ LHS::Interceptors::AutoOauth::ThreadRegistry.access_token = instance_exec(&LHS.config.auto_oauth)
18
+ end
19
+
20
+ def lhs_check_auto_oauth_enabled!
21
+ return if LHS.config.auto_oauth.present? && LHS.config.auto_oauth.is_a?(Proc)
22
+ raise 'You have to enable LHS.config.auto_oauth by passing a proc returning an access token!'
23
+ end
24
+ end
25
+ end
@@ -64,11 +64,11 @@ class LHS::Record
64
64
  chain
65
65
  end
66
66
 
67
- def includes(*args)
67
+ def includes_first_page(*args)
68
68
  Chain.new(self, Include.new(Chain.unfold(args)))
69
69
  end
70
70
 
71
- def includes_all(*args)
71
+ def includes(*args)
72
72
  chain = Chain.new(self, Include.new(Chain.unfold(args)))
73
73
  chain.include_all!(args)
74
74
  chain
@@ -259,11 +259,11 @@ class LHS::Record
259
259
  push(ErrorHandling.new(error_class => handler))
260
260
  end
261
261
 
262
- def includes(*args)
262
+ def includes_first_page(*args)
263
263
  push(Include.new(Chain.unfold(args)))
264
264
  end
265
265
 
266
- def includes_all(*args)
266
+ def includes(*args)
267
267
  chain = push(Include.new(Chain.unfold(args)))
268
268
  chain.include_all!(args)
269
269
  chain
@@ -12,45 +12,62 @@ class LHS::Record
12
12
  mattr_accessor :configuration
13
13
 
14
14
  module ClassMethods
15
- def configuration(args)
16
- @configuration = args.freeze || {}
15
+ def configuration(args = nil)
16
+ if !args.nil?
17
+ @configuration = args
18
+ else
19
+ @configuration || {}
20
+ end
21
+ end
22
+
23
+ def auto_oauth?
24
+ LHS.config.auto_oauth && configuration && auto_oauth
25
+ end
26
+
27
+ def auto_oauth
28
+ configuration.fetch(:auto_oauth, false)
29
+ end
30
+
31
+ def oauth(provider = nil)
32
+ value = provider || true
33
+ configuration.present? ? configuration.merge!(auto_oauth: value) : configuration(auto_oauth: value)
17
34
  end
18
35
 
19
36
  def item_key
20
37
  symbolize_unless_complex(
21
- @configuration.try(:[], :item_key) || :item
38
+ configuration.dig(:item_key) || :item
22
39
  )
23
40
  end
24
41
 
25
42
  def items_key
26
43
  symbolize_unless_complex(
27
- @configuration.try(:[], :items_key) || :items
44
+ configuration.dig(:items_key) || :items
28
45
  )
29
46
  end
30
47
 
31
48
  def item_created_key
32
49
  symbolize_unless_complex(
33
- @configuration.try(:[], :item_created_key)
50
+ configuration.dig(:item_created_key)
34
51
  )
35
52
  end
36
53
 
37
54
  def limit_key(type = nil)
38
55
  symbolize_unless_complex(
39
- pagination_parameter(@configuration.try(:[], :limit_key), type) ||
56
+ pagination_parameter(configuration.dig(:limit_key), type) ||
40
57
  :limit
41
58
  )
42
59
  end
43
60
 
44
61
  def total_key
45
62
  symbolize_unless_complex(
46
- @configuration.try(:[], :total_key) || :total
63
+ configuration.dig(:total_key) || :total
47
64
  )
48
65
  end
49
66
 
50
67
  # Key used for determine current page
51
68
  def pagination_key(type = nil)
52
69
  symbolize_unless_complex(
53
- pagination_parameter(@configuration.try(:[], :pagination_key), type) ||
70
+ pagination_parameter(configuration.dig(:pagination_key), type) ||
54
71
  :offset
55
72
  )
56
73
  end
@@ -58,15 +75,15 @@ class LHS::Record
58
75
  # Strategy used for calculationg next pages and navigate pages
59
76
  def pagination_strategy
60
77
  symbolize_unless_complex(
61
- @configuration.try(:[], :pagination_strategy) || :offset
78
+ configuration.dig(:pagination_strategy) || :offset
62
79
  )
63
80
  end
64
81
 
65
82
  # Allows record to be configured as not paginated,
66
83
  # as by default it's considered paginated
67
84
  def paginated
68
- return true if @configuration.blank?
69
- @configuration.fetch(:paginated, true)
85
+ return true if configuration.blank?
86
+ configuration.fetch(:paginated, true)
70
87
  end
71
88
 
72
89
  private
@@ -517,20 +517,39 @@ class LHS::Record
517
517
  options[:url] = compute_url!(options[:params]) unless options.key?(:url)
518
518
  merge_explicit_params!(options[:params])
519
519
  options.delete(:params) if options[:params]&.empty?
520
- inject_request_cycle_cache!(options)
520
+ inject_interceptors!(options)
521
521
  options
522
522
  end
523
523
 
524
- # Injects options into request, that enable the request cycle cache interceptor
525
- def inject_request_cycle_cache!(options)
526
- return unless LHS.config.request_cycle_cache_enabled
524
+ def inject_interceptors!(options)
525
+ if LHS.config.request_cycle_cache_enabled
526
+ inject_interceptor!(
527
+ options,
528
+ LHS::Interceptors::RequestCycleCache::Interceptor,
529
+ LHC::Caching,
530
+ "[WARNING] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/README.md#caching-interceptor)!"
531
+ )
532
+ end
533
+
534
+ endpoint = find_endpoint(options[:params], options.fetch(:url, nil))
535
+ if auto_oauth? || (endpoint.options&.dig(:oauth) && LHS.config.auto_oauth) || options[:oauth]
536
+ inject_interceptor!(
537
+ options.merge!(record: self),
538
+ LHS::Interceptors::AutoOauth::Interceptor,
539
+ LHC::Auth,
540
+ "[WARNING] Can't enable auto oauth as LHC::Auth interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/README.md#authentication-interceptor)!"
541
+ )
542
+ end
543
+ end
544
+
545
+ def inject_interceptor!(options, interceptor, dependecy, warning)
527
546
  interceptors = options[:interceptors] || LHC.config.interceptors
528
- if interceptors.include?(LHC::Caching)
547
+ if interceptors.include?(dependecy)
529
548
  # Ensure interceptor is prepend
530
- interceptors = interceptors.unshift(LHS::Interceptors::RequestCycleCache::Interceptor)
549
+ interceptors = interceptors.unshift(interceptor)
531
550
  options[:interceptors] = interceptors
532
551
  else
533
- warn("[WARNING] Can't enable request cycle cache as LHC::Caching interceptor is not enabled/configured (see https://github.com/local-ch/lhc/blob/master/docs/interceptors/caching.md#caching-interceptor)!")
552
+ warn(warning)
534
553
  end
535
554
  end
536
555
 
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ class LHS::Record
6
+
7
+ module Update
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ class <<self
12
+ alias_method :update, :create
13
+ alias_method :update!, :create!
14
+ end
15
+ end
16
+ end
17
+ end
@@ -5,7 +5,7 @@ require 'singleton'
5
5
  class LHS::Config
6
6
  include Singleton
7
7
 
8
- attr_accessor :request_cycle_cache_enabled, :request_cycle_cache, :trace
8
+ attr_accessor :request_cycle_cache_enabled, :request_cycle_cache, :trace, :auto_oauth
9
9
 
10
10
  def initialize
11
11
  self.request_cycle_cache_enabled ||= true
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module LHS
6
+ module Interceptors
7
+ module AutoOauth
8
+ extend ActiveSupport::Concern
9
+
10
+ class Interceptor < LHC::Interceptor
11
+
12
+ def before_request
13
+ request.options[:auth] = { bearer: token }
14
+ end
15
+
16
+ def tokens
17
+ @tokens ||= LHS::Interceptors::AutoOauth::ThreadRegistry.access_token
18
+ end
19
+
20
+ def token
21
+ if tokens.is_a?(Hash)
22
+ tokens.dig(
23
+ request.options[:oauth] ||
24
+ request.options[:record]&.auto_oauth
25
+ )
26
+ else
27
+ tokens
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/per_thread_registry'
5
+
6
+ module LHS
7
+ module Interceptors
8
+ module AutoOauth
9
+ extend ActiveSupport::Concern
10
+ class ThreadRegistry
11
+ # Using ActiveSupports PerThreadRegistry to be able to support Active Support v4.
12
+ # Will switch to thread_mattr_accessor (which comes with Activesupport) when we dropping support for Active Support v4.
13
+ extend ActiveSupport::PerThreadRegistry
14
+ attr_accessor :access_token
15
+ end
16
+ end
17
+ end
18
+ end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class LHS::Record
4
+ autoload :AttributeAssignment,
5
+ 'lhs/concerns/record/attribute_assignment'
4
6
  autoload :Batch,
5
7
  'lhs/concerns/record/batch'
6
8
  autoload :Chainable,
@@ -45,9 +47,10 @@ class LHS::Record
45
47
  'lhs/concerns/record/scope'
46
48
  autoload :Tracing,
47
49
  'lhs/concerns/record/tracing'
48
- autoload :AttributeAssignment,
49
- 'lhs/concerns/record/attribute_assignment'
50
+ autoload :Update,
51
+ 'lhs/concerns/record/update'
50
52
 
53
+ include AttributeAssignment
51
54
  include Batch
52
55
  include Chainable
53
56
  include Configuration
@@ -72,7 +75,7 @@ class LHS::Record
72
75
  include Relations
73
76
  include Scope
74
77
  include Tracing
75
- include AttributeAssignment
78
+ include Update
76
79
 
77
80
  delegate :_proxy, :_endpoint, :merge_raw!, :select, :becomes, :respond_to?, to: :_data
78
81
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LHS
4
- VERSION = '21.2.4'
4
+ VERSION = '22.1.0'
5
5
  end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ describe 'Auto OAuth Authentication', type: :request, dummy_models: true do
6
+
7
+ context 'without LHC::Auth interceptor enabled' do
8
+
9
+ before do
10
+ LHS.configure do |config|
11
+ config.auto_oauth = -> { access_token }
12
+ end
13
+ end
14
+
15
+ it 'shows a warning that it can not perform auto authentication' do
16
+ expect(lambda do
17
+ get '/automatic_authentication/oauth'
18
+ end).to output(
19
+ %r{\[WARNING\] Can't enable auto oauth as LHC::Auth interceptor is not enabled\/configured \(see https://github.com/local-ch/lhc/blob/master/README.md#authentication-interceptor\)!}
20
+ ).to_stderr
21
+ end
22
+ end
23
+
24
+ context 'with LHC::Auth interceptor enabled' do
25
+
26
+ context 'with only one auth provider' do
27
+
28
+ let(:token) { ApplicationController::ACCESS_TOKEN }
29
+
30
+ let(:record_request) do
31
+ stub_request(:get, "http://datastore/v2/records_with_oauth/1")
32
+ .with(
33
+ headers: { 'Authorization' => "Bearer #{token}" }
34
+ ).to_return(status: 200, body: { name: 'Record' }.to_json)
35
+ end
36
+
37
+ let(:records_request) do
38
+ stub_request(:get, "http://datastore/v2/records_with_oauth?color=blue")
39
+ .with(
40
+ headers: { 'Authorization' => "Bearer #{token}" }
41
+ ).to_return(status: 200, body: { items: [{ name: 'Record' }] }.to_json)
42
+ end
43
+
44
+ before do
45
+ LHS.configure do |config|
46
+ config.auto_oauth = -> { access_token }
47
+ end
48
+ LHC.configure do |config|
49
+ config.interceptors = [LHC::Auth]
50
+ end
51
+ record_request
52
+ records_request
53
+ end
54
+
55
+ after do
56
+ LHC.config.reset
57
+ end
58
+
59
+ it 'applies OAuth credentials for the individual request automatically' do
60
+ get '/automatic_authentication/oauth'
61
+ expect(record_request).to have_been_requested
62
+ expect(records_request).to have_been_requested
63
+ end
64
+ end
65
+
66
+ context 'with multiple auth providers' do
67
+
68
+ before do
69
+ LHS.configure do |config|
70
+ config.auto_oauth = proc do
71
+ {
72
+ provider1: access_token_provider_1,
73
+ provider2: access_token_provider_2
74
+ }
75
+ end
76
+ end
77
+ LHC.configure do |config|
78
+ config.interceptors = [LHC::Auth]
79
+ end
80
+ record_request_provider_1
81
+ records_request_provider_2
82
+ records_request_per_endpoint_provider_1
83
+ record_request_per_endpoint_provider_2
84
+ end
85
+
86
+ let(:token) { ApplicationController::ACCESS_TOKEN }
87
+
88
+ let(:record_request_provider_1) do
89
+ stub_request(:get, "http://datastore/v2/records_with_multiple_oauth_providers_1/1")
90
+ .with(
91
+ headers: { 'Authorization' => "Bearer #{token}_provider_1" }
92
+ ).to_return(status: 200, body: { name: 'Record' }.to_json)
93
+ end
94
+
95
+ let(:records_request_provider_2) do
96
+ stub_request(:get, "http://datastore/v2/records_with_multiple_oauth_providers_2?color=blue")
97
+ .with(
98
+ headers: { 'Authorization' => "Bearer #{token}_provider_2" }
99
+ ).to_return(status: 200, body: { items: [{ name: 'Record' }] }.to_json)
100
+ end
101
+
102
+ let(:records_request_per_endpoint_provider_1) do
103
+ stub_request(:get, "http://datastore/v2/records_with_multiple_oauth_providers_per_endpoint?color=blue")
104
+ .with(
105
+ headers: { 'Authorization' => "Bearer #{token}_provider_1" }
106
+ ).to_return(status: 200, body: { items: [{ name: 'Record' }] }.to_json)
107
+ end
108
+
109
+ let(:record_request_per_endpoint_provider_2) do
110
+ stub_request(:get, "http://datastore/v2/records_with_multiple_oauth_providers_per_endpoint/1")
111
+ .with(
112
+ headers: { 'Authorization' => "Bearer #{token}_provider_2" }
113
+ ).to_return(status: 200, body: { name: 'Record' }.to_json)
114
+ end
115
+
116
+ after do
117
+ LHC.config.reset
118
+ end
119
+
120
+ it 'applies OAuth credentials for the individual request automatically no matter how many auth providers are configured ' do
121
+ get '/automatic_authentication/oauth_with_multiple_providers'
122
+ expect(record_request_provider_1).to have_been_requested
123
+ expect(records_request_provider_2).to have_been_requested
124
+ expect(records_request_per_endpoint_provider_1).to have_been_requested
125
+ expect(record_request_per_endpoint_provider_2).to have_been_requested
126
+ end
127
+ end
128
+
129
+ context 'with provider enabled for auto oauth' do
130
+
131
+ let(:token) { ApplicationController::ACCESS_TOKEN }
132
+
133
+ let(:record_request) do
134
+ stub_request(:get, "http://internalservice/v2/records/1")
135
+ .with(
136
+ headers: { 'Authorization' => "Bearer #{token}" }
137
+ ).to_return(status: 200, body: { name: 'Record' }.to_json)
138
+ end
139
+
140
+ let(:records_request) do
141
+ stub_request(:get, "http://internalservice/v2/records?color=blue")
142
+ .with(
143
+ headers: { 'Authorization' => "Bearer #{token}" }
144
+ ).to_return(status: 200, body: { items: [{ name: 'Record' }] }.to_json)
145
+ end
146
+
147
+ before do
148
+ LHS.configure do |config|
149
+ config.auto_oauth = -> { access_token }
150
+ end
151
+ LHC.configure do |config|
152
+ config.interceptors = [LHC::Auth]
153
+ end
154
+ record_request
155
+ records_request
156
+ end
157
+
158
+ after do
159
+ LHC.config.reset
160
+ end
161
+
162
+ it 'applies OAuth credentials for the individual request automatically' do
163
+ get '/automatic_authentication/oauth_with_provider'
164
+ expect(record_request).to have_been_requested
165
+ expect(records_request).to have_been_requested
166
+ end
167
+ end
168
+ end
169
+ end