lhs 21.3.0.pre.autoauth.1 → 22.1.1.pre
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 +4 -4
- data/README.md +205 -117
- data/lhs.gemspec +1 -1
- data/lib/lhs/concerns/record/chainable.rb +4 -4
- data/lib/lhs/concerns/record/configuration.rb +23 -14
- data/lib/lhs/concerns/record/request.rb +29 -32
- data/lib/lhs/concerns/record/update.rb +17 -0
- data/lib/lhs/interceptors/auto_oauth/interceptor.rb +16 -1
- data/lib/lhs/record.rb +6 -3
- data/lib/lhs/version.rb +1 -1
- data/spec/auto_oauth_spec.rb +141 -23
- data/spec/dummy/app/controllers/application_controller.rb +9 -1
- data/spec/dummy/app/controllers/automatic_authentication_controller.rb +18 -0
- data/spec/dummy/app/models/dummy_record_with_auto_oauth_provider.rb +6 -0
- data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers1.rb +7 -0
- data/spec/dummy/app/models/dummy_record_with_multiple_oauth_providers2.rb +7 -0
- data/spec/dummy/app/models/dummy_record_with_multiple_providers_per_endpoint.rb +6 -0
- data/spec/dummy/app/models/providers/internal_services.rb +7 -0
- data/spec/dummy/config/routes.rb +2 -0
- data/spec/item/destroy_spec.rb +1 -1
- data/spec/proxy/record_identification_spec.rb +1 -1
- data/spec/record/all_spec.rb +1 -1
- data/spec/record/endpoints_spec.rb +1 -1
- data/spec/record/handle_includes_errors_spec.rb +1 -1
- data/spec/record/has_many_spec.rb +1 -1
- data/spec/record/has_one_spec.rb +1 -1
- data/spec/record/includes_first_page_spec.rb +727 -0
- data/spec/record/includes_spec.rb +545 -579
- data/spec/record/includes_warning_spec.rb +1 -1
- data/spec/record/mapping_spec.rb +2 -2
- data/spec/record/references_spec.rb +1 -1
- data/spec/record/relation_caching_spec.rb +3 -3
- data/spec/record/update_spec.rb +62 -0
- metadata +21 -10
- data/spec/dummy/config/initializers/lhs.rb +0 -3
- data/spec/record/includes_all_spec.rb +0 -693
@@ -187,7 +187,7 @@ class LHS::Record
|
|
187
187
|
options = extend_with_reference(options, reference)
|
188
188
|
addition = load_include(options, data, sub_includes, reference)
|
189
189
|
extend_raw_data!(data, addition, included)
|
190
|
-
expand_addition!(data, included, options)
|
190
|
+
expand_addition!(data, included, options) unless expanded_data?(addition)
|
191
191
|
end
|
192
192
|
end
|
193
193
|
|
@@ -208,25 +208,21 @@ class LHS::Record
|
|
208
208
|
options = extend_with_reference(options, reference.except(:url))
|
209
209
|
record = record_for_options(options) || self
|
210
210
|
options = convert_options_to_endpoints(options) if record_for_options(options)
|
211
|
-
expanded_data =
|
212
|
-
record.request(options)
|
213
|
-
rescue LHC::NotFound
|
214
|
-
LHS::Data.new({}, data, record)
|
215
|
-
end
|
211
|
+
expanded_data = record.request(options)
|
216
212
|
extend_raw_data!(data, expanded_data, included)
|
217
213
|
end
|
218
214
|
|
219
|
-
def
|
215
|
+
def expanded_data?(addition)
|
220
216
|
return false if addition.blank?
|
221
217
|
if addition.item?
|
222
|
-
(addition._raw.keys - [:href]).
|
218
|
+
(addition._raw.keys - [:href]).any?
|
223
219
|
elsif addition.collection?
|
224
220
|
addition.all? do |item|
|
225
221
|
next if item.blank?
|
226
222
|
if item._raw.is_a?(Hash)
|
227
|
-
(item._raw.keys - [:href]).
|
223
|
+
(item._raw.keys - [:href]).any?
|
228
224
|
elsif item._raw.is_a?(Array)
|
229
|
-
item.any? { |item| (item._raw.keys - [:href]).
|
225
|
+
item.any? { |item| (item._raw.keys - [:href]).any? }
|
230
226
|
end
|
231
227
|
end
|
232
228
|
end
|
@@ -351,15 +347,11 @@ class LHS::Record
|
|
351
347
|
def load_include(options, data, sub_includes, references)
|
352
348
|
record = record_for_options(options) || self
|
353
349
|
options = convert_options_to_endpoints(options) if record_for_options(options)
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
load_include_simple!(options, record)
|
360
|
-
end
|
361
|
-
rescue LHC::NotFound
|
362
|
-
LHS::Data.new({}, data, record)
|
350
|
+
prepare_options_for_include_request!(options, sub_includes, references)
|
351
|
+
if references && references[:all] # include all linked resources
|
352
|
+
load_include_all!(options, record, sub_includes, references)
|
353
|
+
else # simply request first page/batch
|
354
|
+
load_include_simple!(options, record)
|
363
355
|
end
|
364
356
|
end
|
365
357
|
|
@@ -522,19 +514,24 @@ class LHS::Record
|
|
522
514
|
end
|
523
515
|
|
524
516
|
def inject_interceptors!(options)
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
517
|
+
if LHS.config.request_cycle_cache_enabled
|
518
|
+
inject_interceptor!(
|
519
|
+
options,
|
520
|
+
LHS::Interceptors::RequestCycleCache::Interceptor,
|
521
|
+
LHC::Caching,
|
522
|
+
"[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)!"
|
523
|
+
)
|
524
|
+
end
|
525
|
+
|
526
|
+
endpoint = find_endpoint(options[:params], options.fetch(:url, nil))
|
527
|
+
if auto_oauth? || (endpoint.options&.dig(:oauth) && LHS.config.auto_oauth) || options[:oauth]
|
528
|
+
inject_interceptor!(
|
529
|
+
options.merge!(record: self),
|
530
|
+
LHS::Interceptors::AutoOauth::Interceptor,
|
531
|
+
LHC::Auth,
|
532
|
+
"[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)!"
|
533
|
+
)
|
534
|
+
end
|
538
535
|
end
|
539
536
|
|
540
537
|
def inject_interceptor!(options, interceptor, dependecy, warning)
|
@@ -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
|
@@ -10,7 +10,22 @@ module LHS
|
|
10
10
|
class Interceptor < LHC::Interceptor
|
11
11
|
|
12
12
|
def before_request
|
13
|
-
request.options[:auth] = { bearer:
|
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
|
14
29
|
end
|
15
30
|
end
|
16
31
|
end
|
data/lib/lhs/record.rb
CHANGED
@@ -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 :
|
49
|
-
'lhs/concerns/record/
|
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
|
78
|
+
include Update
|
76
79
|
|
77
80
|
delegate :_proxy, :_endpoint, :merge_raw!, :select, :becomes, :respond_to?, to: :_data
|
78
81
|
|
data/lib/lhs/version.rb
CHANGED
data/spec/auto_oauth_spec.rb
CHANGED
@@ -6,6 +6,12 @@ describe 'Auto OAuth Authentication', type: :request, dummy_models: true do
|
|
6
6
|
|
7
7
|
context 'without LHC::Auth interceptor enabled' do
|
8
8
|
|
9
|
+
before do
|
10
|
+
LHS.configure do |config|
|
11
|
+
config.auto_oauth = -> { access_token }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
9
15
|
it 'shows a warning that it can not perform auto authentication' do
|
10
16
|
expect(lambda do
|
11
17
|
get '/automatic_authentication/oauth'
|
@@ -16,36 +22,148 @@ describe 'Auto OAuth Authentication', type: :request, dummy_models: true do
|
|
16
22
|
end
|
17
23
|
|
18
24
|
context 'with LHC::Auth interceptor enabled' do
|
19
|
-
let(:record_request) do
|
20
|
-
stub_request(:get, "http://datastore/v2/records_with_oauth/1")
|
21
|
-
.with(
|
22
|
-
headers: { 'Authorization' => "Bearer #{ApplicationController::ACCESS_TOKEN}" }
|
23
|
-
).to_return(status: 200, body: { name: 'Record' }.to_json)
|
24
|
-
end
|
25
25
|
|
26
|
-
|
27
|
-
stub_request(:get, "http://datastore/v2/records_with_oauth?color=blue")
|
28
|
-
.with(
|
29
|
-
headers: { 'Authorization' => "Bearer #{ApplicationController::ACCESS_TOKEN}" }
|
30
|
-
).to_return(status: 200, body: { items: [ { name: 'Record' } ] }.to_json)
|
31
|
-
end
|
26
|
+
context 'with only one auth provider' do
|
32
27
|
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
36
63
|
end
|
37
|
-
record_request
|
38
|
-
records_request
|
39
64
|
end
|
40
65
|
|
41
|
-
|
42
|
-
|
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
|
43
127
|
end
|
44
128
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
49
167
|
end
|
50
168
|
end
|
51
169
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class ApplicationController < ActionController::Base
|
4
4
|
include LHS::OAuth
|
5
|
-
ACCESS_TOKEN = 'token-12345'
|
5
|
+
ACCESS_TOKEN = 'token-12345'
|
6
6
|
|
7
7
|
# Prevent CSRF attacks by raising an exception.
|
8
8
|
# For APIs, you may want to use :null_session instead.
|
@@ -15,4 +15,12 @@ class ApplicationController < ActionController::Base
|
|
15
15
|
def access_token
|
16
16
|
ACCESS_TOKEN
|
17
17
|
end
|
18
|
+
|
19
|
+
def access_token_provider_1
|
20
|
+
"#{ACCESS_TOKEN}_provider_1"
|
21
|
+
end
|
22
|
+
|
23
|
+
def access_token_provider_2
|
24
|
+
"#{ACCESS_TOKEN}_provider_2"
|
25
|
+
end
|
18
26
|
end
|
@@ -8,4 +8,22 @@ class AutomaticAuthenticationController < ApplicationController
|
|
8
8
|
records: DummyRecordWithOauth.where(color: 'blue').as_json
|
9
9
|
}
|
10
10
|
end
|
11
|
+
|
12
|
+
def o_auth_with_multiple_providers
|
13
|
+
render json: {
|
14
|
+
record: DummyRecordWithMultipleOauthProviders1.find(1).as_json,
|
15
|
+
records: DummyRecordWithMultipleOauthProviders2.where(color: 'blue').as_json,
|
16
|
+
per_endpoint: {
|
17
|
+
record: DummyRecordWithMultipleOauthProvidersPerEndpoint.find(1).as_json,
|
18
|
+
records: DummyRecordWithMultipleOauthProvidersPerEndpoint.where(color: 'blue').as_json
|
19
|
+
}
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def o_auth_with_provider
|
24
|
+
render json: {
|
25
|
+
record: DummyRecordWithAutoOauthProvider.find(1).as_json,
|
26
|
+
records: DummyRecordWithAutoOauthProvider.where(color: 'blue').as_json
|
27
|
+
}
|
28
|
+
end
|
11
29
|
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class DummyRecordWithMultipleOauthProviders1 < LHS::Record
|
4
|
+
oauth(:provider1)
|
5
|
+
endpoint 'http://datastore/v2/records_with_multiple_oauth_providers_1'
|
6
|
+
endpoint 'http://datastore/v2/records_with_multiple_oauth_providers_1/{id}'
|
7
|
+
end
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class DummyRecordWithMultipleOauthProviders2 < LHS::Record
|
4
|
+
oauth(:provider2)
|
5
|
+
endpoint 'http://datastore/v2/records_with_multiple_oauth_providers_2'
|
6
|
+
endpoint 'http://datastore/v2/records_with_multiple_oauth_providers_2/{id}'
|
7
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class DummyRecordWithMultipleOauthProvidersPerEndpoint < LHS::Record
|
4
|
+
endpoint 'http://datastore/v2/records_with_multiple_oauth_providers_per_endpoint', oauth: :provider1
|
5
|
+
endpoint 'http://datastore/v2/records_with_multiple_oauth_providers_per_endpoint/{id}', oauth: :provider2
|
6
|
+
end
|