Skip to content

Commit fdd0c69

Browse files
authored
Merge pull request #368 from 3scale/openapi-support-multiple-flows
THREESCALE-9768 OpenAPI support for multiple flows in oauth
2 parents b0c3b57 + 3c9ca65 commit fdd0c69

8 files changed

Lines changed: 149 additions & 36 deletions

File tree

docs/openapi.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ $ tool_to_read_openapi_from_source | 3scale import openapi -d <destination> -
7272
* Only first `servers[0].url` element in `servers` list parsed as *private base url*. As OpenAPI specification`basePath` property, `servers[0].url` URL's base path component will be used.
7373
* Toolbox will *not* parse servers in path item or operation objects.
7474
* Supported security schemes: apiKey, oauth2 (any flow type).
75-
* Multiple flows in security scheme object not supported.
7675

7776
### Importing 3scale Backend
7877

lib/3scale_toolbox/commands/import_command/openapi/update_service_oidc_conf_step.rb

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,7 @@ def add_flow_settings(settings)
2929
# only applies to oauth2 sec type
3030
return if api_spec.security.nil? || api_spec.security[:type] != 'oauth2'
3131

32-
oidc_configuration = {
33-
standard_flow_enabled: false,
34-
implicit_flow_enabled: false,
35-
service_accounts_enabled: false,
36-
direct_access_grants_enabled: false
37-
}.merge(api_spec.security[:flow] => true)
38-
settings.merge!(oidc_configuration)
32+
settings.merge!(api_spec.security[:flows] || {})
3933
end
4034
end
4135
end

lib/3scale_toolbox/openapi/oas3.rb

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ module OpenAPI
2121
# * :type -> string
2222
# * :name -> string
2323
# * :in_f -> string
24-
# * :flow -> symbol (:implicit_flow_enabled, :direct_access_grants_enabled, :service_accounts_enabled, :standard_flow_enabled)
24+
# * :flows -> hash
25+
# * :implicit_flow_enabled -> bool
26+
# * :direct_access_grants_enabled -> bool
27+
# * :service_accounts_enabled -> bool
28+
# * :standard_flow_enabled -> bool
2529
# * :scopes -> array of string
2630
# * OAS3.service_backend_version -> string ('1','2','oidc')
2731
# * OAS3.set_server_url -> def(spec, url)
@@ -103,9 +107,10 @@ def set_oauth2_urls(spec, sec_scheme_id, authorization_url, token_url)
103107
raise ThreeScaleToolbox::Error, "Expected security scheme {#{sec_scheme_id}} not found or not oauth2"
104108
end
105109

106-
flow_key, flow_obj = sec_scheme_obj['flows'].first
107-
flow_obj['authorizationUrl'] = authorization_url if %w[implicit authorizationCode].include?(flow_key)
108-
flow_obj['tokenUrl'] = token_url if %w[password clientCredentials authorizationCode].include?(flow_key)
110+
sec_scheme_obj['flows'].each do |flow_key, flow_obj|
111+
flow_obj['authorizationUrl'] = authorization_url if %w[implicit authorizationCode].include?(flow_key)
112+
flow_obj['tokenUrl'] = token_url if %w[password clientCredentials authorizationCode].include?(flow_key)
113+
end
109114
end
110115

111116
def set_server_url(spec, url)
@@ -184,7 +189,7 @@ def parse_global_security_reqs
184189
type: sec_def['type'],
185190
name: sec_def['name'],
186191
in_f: sec_def['in'],
187-
flow: parse_flows(sec_def['flows']),
192+
flows: parse_flows(sec_def['flows']),
188193
scopes: sec_item
189194
}
190195
end
@@ -208,9 +213,18 @@ def security_schemes
208213
def parse_flows(flows_object)
209214
return nil if flows_object.nil?
210215

211-
raise ThreeScaleToolbox::Error, 'Invalid OAS: multiple flows' if flows_object.size > 1
216+
flows_object.keys.reduce(basic_flows_object) do |obj, key|
217+
obj.merge!(convert_flow(key) => true)
218+
end
219+
end
212220

213-
convert_flow(flows_object.keys.first)
221+
def basic_flows_object
222+
{
223+
standard_flow_enabled: false,
224+
implicit_flow_enabled: false,
225+
service_accounts_enabled: false,
226+
direct_access_grants_enabled: false
227+
}
214228
end
215229

216230
def convert_flow(flow_name)

lib/3scale_toolbox/openapi/swagger.rb

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,11 @@ module OpenAPI
2121
# * :type -> string
2222
# * :name -> string
2323
# * :in_f -> string
24-
# * :flow -> symbol (:implicit_flow_enabled, :direct_access_grants_enabled, :service_accounts_enabled, :standard_flow_enabled)
24+
# * :flows -> hash
25+
# * :implicit_flow_enabled -> bool
26+
# * :direct_access_grants_enabled -> bool
27+
# * :service_accounts_enabled -> bool
28+
# * :standard_flow_enabled -> bool
2529
# * :scopes -> array of string
2630
# * Swagger.service_backend_version -> string ('1','2','oidc')
2731
# * Swagger.set_server_url -> def(spec, url)
@@ -150,7 +154,7 @@ def parse_global_security_reqs
150154
type: sec_def['type'],
151155
name: sec_def['name'],
152156
in_f: sec_def['in'],
153-
flow: convert_flow(sec_def['flow']),
157+
flows: parse_flows(sec_def['flow']),
154158
scopes: sec_item
155159
}
156160
end
@@ -171,6 +175,21 @@ def security_definitions
171175
raw['securityDefinitions'] || {}
172176
end
173177

178+
def parse_flows(flow_name)
179+
return nil if flow_name.nil?
180+
181+
basic_flows_object.merge!({ convert_flow(flow_name) => true })
182+
end
183+
184+
def basic_flows_object
185+
{
186+
standard_flow_enabled: false,
187+
implicit_flow_enabled: false,
188+
service_accounts_enabled: false,
189+
direct_access_grants_enabled: false
190+
}
191+
end
192+
174193
def convert_flow(flow_name)
175194
return nil if flow_name.nil?
176195

spec/shared_oas3_contexts.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,16 @@
345345
petstore_oauth:
346346
type: oauth2
347347
flows:
348+
clientCredentials:
349+
tokenUrl: http://example.org/api/oauth/dialog
350+
scopes:
351+
write:pets: modify pets in your account
352+
read:pets: read your pets
353+
authorizationCode:
354+
authorizationUrl: https://example.com/api/oauth/dialog
355+
tokenUrl: http://example.org/api/oauth/token
356+
scopes:
357+
write:pets: modify pets in your account
348358
implicit:
349359
authorizationUrl: http://example.org/api/oauth/dialog
350360
scopes:

spec/unit/commands/import_command/openapi/update_service_oidc_conf_spec.rb

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
{
2121
target: service,
2222
api_spec: api_spec,
23-
logger: logger,
23+
logger: logger
2424
}
2525
end
2626

@@ -56,35 +56,68 @@
5656
let(:expected_implicit_flow) { false }
5757
let(:expected_service_accounts) { false }
5858
let(:expected_direct_access_grants) { false }
59-
let(:security) { { id: 'oidc', type: 'oauth2', flow: flow } }
59+
let(:basic_empty_flow) do
60+
{
61+
standard_flow_enabled: false, implicit_flow_enabled: false,
62+
service_accounts_enabled: false, direct_access_grants_enabled: false
63+
}
64+
end
65+
66+
let(:security) { { id: 'oidc', type: 'oauth2', flows: flows } }
67+
68+
context 'no flows' do
69+
let(:flows) { nil }
70+
71+
it 'service is not updated' do
72+
# if service.update_oidc is called, this test should fail
73+
subject
74+
end
75+
end
6076

6177
context 'flow implicit' do
62-
let(:flow) { :implicit_flow_enabled }
78+
let(:flows) { basic_empty_flow.merge(implicit_flow_enabled: true) }
6379
let(:expected_implicit_flow) { true }
6480

6581
it_behaves_like 'oidc is updated with required flow'
6682
end
6783

6884
context 'flow password' do
69-
let(:flow) { :direct_access_grants_enabled }
85+
let(:flows) { basic_empty_flow.merge(direct_access_grants_enabled: true) }
7086
let(:expected_direct_access_grants) { true }
7187

7288
it_behaves_like 'oidc is updated with required flow'
7389
end
7490

7591
context 'flow application' do
76-
let(:flow) { :service_accounts_enabled }
92+
let(:flows) { basic_empty_flow.merge(service_accounts_enabled: true) }
7793
let(:expected_service_accounts) { true }
7894

7995
it_behaves_like 'oidc is updated with required flow'
8096
end
8197

8298
context 'flow accessCode' do
83-
let(:flow) { :standard_flow_enabled }
99+
let(:flows) { basic_empty_flow.merge(standard_flow_enabled: true) }
84100
let(:expected_standard_flow) { true }
85101

86102
it_behaves_like 'oidc is updated with required flow'
87103
end
104+
105+
context 'all flows' do
106+
let(:flows) do
107+
{
108+
standard_flow_enabled: true,
109+
implicit_flow_enabled: true,
110+
service_accounts_enabled: true,
111+
direct_access_grants_enabled: true
112+
}
113+
end
114+
let(:expected_standard_flow) { true }
115+
let(:expected_service_accounts) { true }
116+
let(:expected_direct_access_grants) { true }
117+
let(:expected_implicit_flow) { true }
118+
119+
it_behaves_like 'oidc is updated with required flow'
120+
end
88121
end
89122
end
90123
end

spec/unit/openapi/oas3_spec.rb

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@
66
let(:path) { '/path/to/petstore.yaml' }
77
subject { described_class.build(path, raw_specification, validate: validate) }
88
let(:content) { basic_oas3_content }
9+
let(:basic_empty_flow) do
10+
{
11+
standard_flow_enabled: false, implicit_flow_enabled: false,
12+
service_accounts_enabled: false, direct_access_grants_enabled: false
13+
}
14+
end
915

1016
context 'missing info' do
1117
let(:content) do
@@ -262,7 +268,7 @@
262268
end
263269

264270
it 'flow matches' do
265-
expect(subject.security[:flow]).to be_nil
271+
expect(subject.security[:flows]).to be_nil
266272
end
267273

268274
it 'scopes matches' do
@@ -294,7 +300,7 @@
294300
end
295301

296302
it 'flow matches' do
297-
expect(subject.security[:flow]).to be(:implicit_flow_enabled)
303+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(implicit_flow_enabled: true))
298304
end
299305

300306
it 'scopes matches' do
@@ -326,7 +332,7 @@
326332
end
327333

328334
it 'flow matches' do
329-
expect(subject.security[:flow]).to be(:direct_access_grants_enabled)
335+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(direct_access_grants_enabled: true))
330336
end
331337

332338
it 'scopes matches' do
@@ -358,7 +364,7 @@
358364
end
359365

360366
it 'flow matches' do
361-
expect(subject.security[:flow]).to be(:service_accounts_enabled)
367+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(service_accounts_enabled: true))
362368
end
363369

364370
it 'scopes matches' do
@@ -390,7 +396,7 @@
390396
end
391397

392398
it 'flow matches' do
393-
expect(subject.security[:flow]).to be(:standard_flow_enabled)
399+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(standard_flow_enabled: true))
394400
end
395401

396402
it 'scopes matches' do
@@ -459,9 +465,41 @@
459465
context 'multiple flow security schema' do
460466
let(:content) { oauth2_multiple_flow_oas3_content }
461467

462-
it 'parsing raises error' do
463-
expect { subject.security }.to raise_error(ThreeScaleToolbox::Error,
464-
/Invalid OAS: multiple flows/)
468+
it 'matches oidc version' do
469+
expect(subject.service_backend_version).to eq('oidc')
470+
end
471+
472+
it 'available' do
473+
expect(subject.security).not_to be_nil
474+
end
475+
476+
it 'id matches' do
477+
expect(subject.security[:id]).to eq('petstore_oauth')
478+
end
479+
480+
it 'type matches' do
481+
expect(subject.security[:type]).to eq('oauth2')
482+
end
483+
484+
it 'name matches' do
485+
expect(subject.security[:name]).to be_nil
486+
end
487+
488+
it 'in_f matches' do
489+
expect(subject.security[:in_f]).to be_nil
490+
end
491+
492+
it 'flow matches' do
493+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge({
494+
implicit_flow_enabled: true,
495+
direct_access_grants_enabled: true,
496+
service_accounts_enabled: true,
497+
standard_flow_enabled: true
498+
}))
499+
end
500+
501+
it 'scopes matches' do
502+
expect(subject.security[:scopes]).to match_array(['write:pets', 'read:pets'])
465503
end
466504
end
467505
end

spec/unit/openapi/swagger_spec.rb

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
let(:validate) { true }
66
subject { described_class.build(raw_specification, validate: validate) }
77
let(:content) { basic_swagger_content }
8+
let(:basic_empty_flow) do
9+
{
10+
standard_flow_enabled: false, implicit_flow_enabled: false,
11+
service_accounts_enabled: false, direct_access_grants_enabled: false
12+
}
13+
end
814

915
context 'missing info' do
1016
let(:content) do
@@ -217,7 +223,7 @@
217223
end
218224

219225
it 'flow matches' do
220-
expect(subject.security[:flow]).to be_nil
226+
expect(subject.security[:flows]).to be_nil
221227
end
222228

223229
it 'scopes matches' do
@@ -249,7 +255,7 @@
249255
end
250256

251257
it 'flow matches' do
252-
expect(subject.security[:flow]).to be(:implicit_flow_enabled)
258+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(implicit_flow_enabled: true))
253259
end
254260

255261
it 'scopes matches' do
@@ -281,7 +287,7 @@
281287
end
282288

283289
it 'flow matches' do
284-
expect(subject.security[:flow]).to be(:direct_access_grants_enabled)
290+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(direct_access_grants_enabled: true))
285291
end
286292

287293
it 'scopes matches' do
@@ -313,7 +319,7 @@
313319
end
314320

315321
it 'flow matches' do
316-
expect(subject.security[:flow]).to be(:service_accounts_enabled)
322+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(service_accounts_enabled: true))
317323
end
318324

319325
it 'scopes matches' do
@@ -345,7 +351,7 @@
345351
end
346352

347353
it 'flow matches' do
348-
expect(subject.security[:flow]).to be(:standard_flow_enabled)
354+
expect(subject.security[:flows]).to eq(basic_empty_flow.merge(standard_flow_enabled: true))
349355
end
350356

351357
it 'scopes matches' do

0 commit comments

Comments
 (0)