lhs 21.2.4 → 22.1.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.
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