openapi-sourcetools 0.8.0 → 0.8.1
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/bin/openapi-addsecurityschemes +108 -0
- data/bin/openapi-generate +3 -3
- data/lib/openapi/sourcetools/gen.rb +57 -65
- data/lib/openapi/sourcetools/generate.rb +0 -1
- data/lib/openapi/sourcetools/loaders.rb +9 -10
- data/lib/openapi/sourcetools/version.rb +1 -1
- data/lib/openapi/sourcetools.rb +1 -0
- metadata +4 -3
- data/lib/openapi/sourcetools/securityschemes.rb +0 -268
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 93117ebfa33920b762da40afe48264ead793574ad336628487d3cfb3b6019987
|
4
|
+
data.tar.gz: 67cb3400cd879b3625e58b72db11cfc860a87179d069ff341a6202391f8d61f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af4681c719424375326d4149ede787f0d6a58d33ff28082d7c1f7b00db4b40186f6406bfe99707012aeae0ac03d4504fb14cd49b6e587f55d91c1f2cb353a3b3
|
7
|
+
data.tar.gz: f6c84c0a9e6b20040fefa9611c8abf211abc1fb65c6a3c3041ce1f96f3b3482fa1789eb2eb16460c5a1d892d56fd843164ae4d7dfb9c4cdb6e3a13f26977d323
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# Copyright © 2025 Ismo Kärkkäinen
|
5
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
6
|
+
|
7
|
+
require_relative '../lib/openapi/sourcetools/apiobjects'
|
8
|
+
require_relative '../lib/openapi/sourcetools/common'
|
9
|
+
require 'optparse'
|
10
|
+
include OpenAPISourceTools
|
11
|
+
|
12
|
+
|
13
|
+
def add_combinations(combinations, security)
|
14
|
+
security.each do |s|
|
15
|
+
subset = s.keys.sort!
|
16
|
+
next if subset.empty?
|
17
|
+
combinations.add(subset)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def gather_security(doc)
|
22
|
+
combinations = Set.new
|
23
|
+
security = doc.fetch('security', [])
|
24
|
+
add_combinations(combinations, security)
|
25
|
+
operation_objects = []
|
26
|
+
doc.fetch('paths', {}).each_value do |item|
|
27
|
+
oos = ApiObjects.operation_objects(item).values
|
28
|
+
oos.each do |oo|
|
29
|
+
add_combinations(combinations, oo['security'] || [])
|
30
|
+
operation_objects.push(oo)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
[security, operation_objects, combinations]
|
34
|
+
end
|
35
|
+
|
36
|
+
def check_security(doc, combinations)
|
37
|
+
singles = Set.new
|
38
|
+
combinations.each do |c|
|
39
|
+
c.each do |s|
|
40
|
+
singles.add(s)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
available = Set.new((doc.dig('components', 'securitySchemes') || {}).keys)
|
44
|
+
missing = false
|
45
|
+
singles.each do |s|
|
46
|
+
unless available.include?(s)
|
47
|
+
$stderr.puts "Security scheme unavailable: #{s}"
|
48
|
+
missing = true
|
49
|
+
end
|
50
|
+
end
|
51
|
+
!missing
|
52
|
+
end
|
53
|
+
|
54
|
+
def namelist2security(item)
|
55
|
+
item.keys.sort!.join(' ')
|
56
|
+
end
|
57
|
+
|
58
|
+
def security2hash(security)
|
59
|
+
out = {}
|
60
|
+
security.each { |s| out[namelist2security(s)] = s }
|
61
|
+
out
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_security_to_operations(operation_objects, top_level_security)
|
65
|
+
operation_objects.each do |oo|
|
66
|
+
sec = security2hash(oo['security'] || top_level_security)
|
67
|
+
oo['security'] = sec.keys.sort!.map { |k| Marshal.load(Marshal.dump(sec[k])) }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def main
|
72
|
+
input_name = nil
|
73
|
+
output_name = nil
|
74
|
+
|
75
|
+
parser = OptionParser.new do |opts|
|
76
|
+
opts.summary_indent = ' '
|
77
|
+
opts.summary_width = 26
|
78
|
+
opts.banner = 'Usage: openapi-addsecurityschemes [options]'
|
79
|
+
opts.separator ''
|
80
|
+
opts.separator 'Options:'
|
81
|
+
opts.on('-i', '--input FILE', 'Read API spec from FILE, not stdin.') do |f|
|
82
|
+
input_name = f
|
83
|
+
end
|
84
|
+
opts.on('-o', '--output FILE', 'Output to FILE, not stdout.') do |f|
|
85
|
+
output_name = f
|
86
|
+
end
|
87
|
+
opts.on('-h', '--help', 'Print this help and exit.') do
|
88
|
+
$stdout.puts %(#{opts}
|
89
|
+
|
90
|
+
Loads API document in OpenAPI format and moves security schemes under components and
|
91
|
+
replaces the original with reference.
|
92
|
+
)
|
93
|
+
exit 0
|
94
|
+
end
|
95
|
+
end
|
96
|
+
parser.order!
|
97
|
+
|
98
|
+
doc = Common.load_source(input_name)
|
99
|
+
return 2 if doc.nil?
|
100
|
+
|
101
|
+
security, operation_objects, combinations = gather_security(doc)
|
102
|
+
return 4 unless check_security(doc, combinations)
|
103
|
+
add_security_to_operations(operation_objects, security)
|
104
|
+
|
105
|
+
Common.dump_result(output_name, doc, 3)
|
106
|
+
end
|
107
|
+
|
108
|
+
exit(main) unless defined?($unit_test)
|
data/bin/openapi-generate
CHANGED
@@ -18,7 +18,7 @@ def main
|
|
18
18
|
|
19
19
|
parser = OptionParser.new do |opts|
|
20
20
|
opts.summary_indent = ' '
|
21
|
-
opts.summary_width =
|
21
|
+
opts.summary_width = 20
|
22
22
|
opts.banner = 'Usage: openapi-generate [options] generator-names...'
|
23
23
|
opts.separator ''
|
24
24
|
opts.separator 'Options:'
|
@@ -37,7 +37,7 @@ Loads API document in OpenAPI format and generator names. Built-in generator
|
|
37
37
|
or additional document loaders accept the following:
|
38
38
|
#{OpenAPISourceTools::Loaders.document.strip}
|
39
39
|
|
40
|
-
During load each generator can add and modify tasks via Gen
|
40
|
+
During load each generator can add and modify tasks via Gen singleton:
|
41
41
|
#{Gen.document.strip}
|
42
42
|
|
43
43
|
After all generators have loaded succesfully, tasks are run.
|
@@ -48,10 +48,10 @@ After all generators have loaded succesfully, tasks are run.
|
|
48
48
|
parser.order!
|
49
49
|
|
50
50
|
return OpenAPISourceTools::Common.aargh('Generator names must be given.', 1) if ARGV.empty?
|
51
|
+
return OpenAPISourceTools::Common.aargh("Not a directory: #{output_dir}", 3) unless File.directory?(output_dir)
|
51
52
|
|
52
53
|
a = OpenAPISourceTools::Common.load_source(input_name)
|
53
54
|
return 2 if a.nil?
|
54
|
-
return OpenAPISourceTools::Common.aargh("Not a directory: #{output_dir}", 3) unless File.directory?(output_dir)
|
55
55
|
gen = OpenAPISourceTools::Generator.new(a, input_name, output_dir, config_prefix)
|
56
56
|
ec = gen.load(ARGV)
|
57
57
|
return ec unless ec.zero?
|
@@ -9,97 +9,77 @@ require_relative 'docs'
|
|
9
9
|
require_relative 'output'
|
10
10
|
require_relative 'config'
|
11
11
|
require 'deep_merge'
|
12
|
-
|
12
|
+
require 'singleton'
|
13
13
|
|
14
14
|
# The generation module that contains things visible to tasks.
|
15
|
-
|
15
|
+
class Gen
|
16
|
+
include Singleton
|
17
|
+
|
18
|
+
@docsrc = []
|
16
19
|
def self.add_doc(symbol, docstr)
|
17
|
-
return if docstr.nil?
|
18
|
-
@docsrc = [] unless instance_variable_defined?('@docsrc')
|
19
20
|
@docsrc.push("- #{symbol} : #{docstr}")
|
20
21
|
end
|
21
22
|
private_class_method :add_doc
|
22
23
|
|
23
|
-
def self.
|
24
|
-
|
24
|
+
def self.attrib_reader(symbol, docstr, initial_value = nil)
|
25
|
+
add_doc(symbol, docstr)
|
25
26
|
attr_reader(symbol)
|
26
|
-
|
27
|
-
instance_variable_set("@#{symbol}",
|
27
|
+
instance_eval("def #{symbol}; Gen.instance.#{symbol}; end")
|
28
|
+
Gen.instance.instance_variable_set("@#{symbol}", initial_value) unless initial_value.nil?
|
28
29
|
end
|
29
|
-
private_class_method :
|
30
|
+
private_class_method :attrib_reader
|
30
31
|
|
31
|
-
def self.
|
32
|
-
read_attr(symbol, default)
|
33
|
-
read_attr(symbol2, default)
|
32
|
+
def self.attrib_accessor(symbol, docstr, initial_value = nil)
|
34
33
|
add_doc(symbol, docstr)
|
35
|
-
end
|
36
|
-
private_class_method :mod_attr2_reader
|
37
|
-
|
38
|
-
def self.mod_attr_reader(symbol, docstr = nil, default = nil)
|
39
|
-
mod_attr2_reader(symbol, nil, docstr, default)
|
40
|
-
end
|
41
|
-
private_class_method :mod_attr_reader
|
42
|
-
|
43
|
-
def self.rw_attr(symbol, default)
|
44
34
|
attr_accessor(symbol)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
instance_variable_set("@#{s}", default)
|
35
|
+
instance_eval("def #{symbol}; Gen.instance.#{symbol}; end")
|
36
|
+
instance_eval("def #{symbol}=(v); Gen.instance.#{symbol} = v; end")
|
37
|
+
Gen.instance.instance_variable_set("@#{symbol}", initial_value) unless initial_value.nil?
|
49
38
|
end
|
50
|
-
private_class_method :
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
mod_attr_accessor :in_name, 'OpenAPI document name, nil if stdin.'
|
72
|
-
mod_attr_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
|
73
|
-
mod_attr_reader :g, 'Hash for storing values visible to all tasks.', {}
|
74
|
-
mod_attr_accessor :x, 'Hash for storing values visible to tasks from processor.', {}
|
75
|
-
mod_attr_accessor :h, 'Instance of class with helper methods.'
|
76
|
-
mod_attr_accessor :tasks, 'Tasks array.', []
|
77
|
-
mod_attr2_accessor :task, :t, 'Current task instance.'
|
78
|
-
mod_attr_accessor :task_index, 'Current task index.'
|
79
|
-
mod_attr_accessor :loaders, 'Array of processor loader methods.', []
|
80
|
-
mod_attr_accessor :output, 'Output-formatting helper.', OpenAPISourceTools::Output.new
|
81
|
-
|
82
|
-
def self.load_config(config_prefix)
|
39
|
+
private_class_method :attrib_accessor
|
40
|
+
|
41
|
+
attrib_reader :doc, 'OpenAPI document.'
|
42
|
+
attrib_reader :outdir, 'Output directory name.'
|
43
|
+
attrib_reader :d, 'Other documents object.', OpenAPISourceTools::Docs.new
|
44
|
+
attrib_reader :wd, 'Original working directory', Dir.pwd
|
45
|
+
attrib_reader :configuration, 'Generator internal configuration'
|
46
|
+
attrib_accessor :config, 'Configuration file name for next gem or Ruby file.'
|
47
|
+
attrib_accessor :separator, 'Key separator in config file names.'
|
48
|
+
attrib_accessor :in_name, 'OpenAPI document name, nil if stdin.'
|
49
|
+
attrib_accessor :in_basename, 'OpenAPI document basename, nil if stdin.'
|
50
|
+
attrib_reader :g, 'Hash for storing values visible to all tasks.', {}
|
51
|
+
attrib_accessor :x, 'Hash for storing values visible to tasks from processor.', {}
|
52
|
+
attrib_accessor :h, 'Instance of class with helper methods.'
|
53
|
+
attrib_accessor :tasks, 'Tasks array.', []
|
54
|
+
attrib_accessor :t, 'Current task instance.'
|
55
|
+
attrib_accessor :task_index, 'Current task index.'
|
56
|
+
attrib_accessor :loaders, 'Array of processor loader methods.', []
|
57
|
+
attrib_accessor :output, 'Output-formatting helper.', OpenAPISourceTools::Output.new
|
58
|
+
|
59
|
+
def self.load_config(name_prefix, extensions = [ '.*' ])
|
83
60
|
cfg = {}
|
84
|
-
cfgs = OpenAPISourceTools::ConfigLoader.find_files(name_prefix:
|
61
|
+
cfgs = OpenAPISourceTools::ConfigLoader.find_files(name_prefix:, extensions:)
|
85
62
|
cfgs = OpenAPISourceTools::ConfigLoader.read_contents(cfgs)
|
86
63
|
cfgs.each { |c| cfg.deep_merge!(c) }
|
87
64
|
cfg
|
88
65
|
end
|
89
|
-
private_class_method :load_config
|
90
66
|
|
91
|
-
def
|
67
|
+
def setup(document_content, input_name, output_directory, config_prefix)
|
92
68
|
@doc = document_content
|
93
69
|
@outdir = output_directory
|
94
70
|
unless input_name.nil?
|
95
71
|
@in_name = File.basename(input_name)
|
96
72
|
@in_basename = File.basename(input_name, '.*')
|
97
73
|
end
|
98
|
-
@configuration = load_config(config_prefix)
|
74
|
+
@configuration = Gen.load_config(config_prefix)
|
99
75
|
add_task(task: OpenAPISourceTools::HelperTask.new)
|
100
76
|
end
|
101
77
|
|
102
|
-
def self.
|
78
|
+
def self.setup(document_content, input_name, output_directory, config_prefix)
|
79
|
+
Gen.instance.setup(document_content, input_name, output_directory, config_prefix)
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_task(task:, name: nil, executable: false, x: nil)
|
103
83
|
@tasks.push(task)
|
104
84
|
# Since this method allows the user to pass their own task type instance,
|
105
85
|
# assign optional values with defaults only when clearly given.
|
@@ -108,15 +88,27 @@ module Gen
|
|
108
88
|
@tasks.last.x = x unless x.nil?
|
109
89
|
end
|
110
90
|
|
111
|
-
def self.
|
91
|
+
def self.add_task(task:, name: nil, executable: false, x: nil)
|
92
|
+
Gen.instance.add_task(task:, name:, executable:, x:)
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_write_content(name:, content:, executable: false)
|
112
96
|
add_task(task: OpenAPISourceTools::WriteTask.new(name, content, executable))
|
113
97
|
end
|
114
98
|
|
115
|
-
def self.
|
99
|
+
def self.add_write_content(name:, content:, executable: false)
|
100
|
+
Gen.instance.add_write_content(name:, content:, executable:)
|
101
|
+
end
|
102
|
+
|
103
|
+
def add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
|
116
104
|
add_task(task: OpenAPISourceTools::Task.new(source, template, template_name),
|
117
105
|
name:, executable:, x:)
|
118
106
|
end
|
119
107
|
|
108
|
+
def self.add(source:, template: nil, template_name: nil, name: nil, executable: false, x: nil)
|
109
|
+
Gen.instance.add(source:, template:, template_name:, name:, executable:, x:)
|
110
|
+
end
|
111
|
+
|
120
112
|
def self.document
|
121
113
|
@docsrc.join("\n") + %(
|
122
114
|
- add_task(task:, name: nil, executable: false, x: nil) : Adds task object.
|
@@ -4,6 +4,7 @@
|
|
4
4
|
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
5
|
|
6
6
|
require_relative 'task'
|
7
|
+
require_relative 'gen'
|
7
8
|
|
8
9
|
|
9
10
|
# Original loader functions. These are accessible via Gen.loaders. New loaders
|
@@ -33,19 +34,17 @@ module OpenAPISourceTools
|
|
33
34
|
true
|
34
35
|
end
|
35
36
|
|
36
|
-
|
37
|
+
EVAL_PREFIX = 'eval:'
|
37
38
|
|
38
|
-
def self.
|
39
|
-
return false unless name.downcase.start_with?(
|
39
|
+
def self.eval_loader(name)
|
40
|
+
return false unless name.downcase.start_with?(EVAL_PREFIX)
|
40
41
|
begin
|
41
42
|
t = OpenAPISourceTools::RestoreProcessorStorage.new({})
|
42
43
|
Gen.tasks.push(t)
|
43
|
-
code = name.slice(
|
44
|
+
code = name.slice(EVAL_PREFIX.size...name.size)
|
44
45
|
eval(code)
|
45
46
|
Gen.config = nil
|
46
47
|
t.x = Gen.x # In case setup code replaced the object.
|
47
|
-
rescue LoadError => e
|
48
|
-
raise StandardError, "Failed to require again #{name}\n#{e}"
|
49
48
|
rescue Exception => e
|
50
49
|
raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
|
51
50
|
end
|
@@ -64,11 +63,11 @@ module OpenAPISourceTools
|
|
64
63
|
Gen.tasks.push(t)
|
65
64
|
base = File.basename(name)
|
66
65
|
Gen.config = base[0..-4] if Gen.config.nil?
|
67
|
-
|
66
|
+
load(File.join(Dir.pwd, base))
|
68
67
|
Gen.config = nil
|
69
68
|
t.x = Gen.x # In case setup code replaced the object.
|
70
69
|
rescue LoadError => e
|
71
|
-
raise StandardError, "Failed to
|
70
|
+
raise StandardError, "Failed to load #{name}\n#{e}"
|
72
71
|
rescue Exception => e
|
73
72
|
raise StandardError, "Problem with #{name}\n#{e}\n#{e.backtrace.join("\n")}"
|
74
73
|
end
|
@@ -139,7 +138,7 @@ module OpenAPISourceTools
|
|
139
138
|
def self.loaders
|
140
139
|
[
|
141
140
|
method(:req_loader),
|
142
|
-
method(:
|
141
|
+
method(:eval_loader),
|
143
142
|
method(:ruby_loader),
|
144
143
|
method(:yaml_loader),
|
145
144
|
method(:bin_loader),
|
@@ -151,7 +150,7 @@ module OpenAPISourceTools
|
|
151
150
|
def self.document
|
152
151
|
<<EOB
|
153
152
|
- #{Loaders::REQ_PREFIX}req_name : requires the gem.
|
154
|
-
- #{Loaders::
|
153
|
+
- #{Loaders::EVAL_PREFIX}code : runs code to add gem tasks again.
|
155
154
|
- ruby_file#{Loaders::RUBY_EXT} : changes to Ruby file directory and requires the file.
|
156
155
|
- #{Loaders::YAML_PREFIX}name:filename : Loads YAML file into Gen.d.name.
|
157
156
|
- name:filename.{#{(Loaders::YAML_EXTS.map { |s| s[1...s.size] }).join('|')}} : Loads YAML file into Gen.d.name.
|
data/lib/openapi/sourcetools.rb
CHANGED
@@ -6,6 +6,7 @@
|
|
6
6
|
require_relative 'sourcetools/task'
|
7
7
|
require_relative 'sourcetools/config'
|
8
8
|
require_relative 'sourcetools/version'
|
9
|
+
require_relative 'sourcetools/apiobjects'
|
9
10
|
# Other modules or classes are exposed via Gen attributes as class instances as needed.
|
10
11
|
# Docs is only needed for run-time storage of whatever loaders can handle.
|
11
12
|
# Loaders array is exposed and can be added to at run-time.
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openapi-sourcetools
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ismo Kärkkäinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-01-
|
11
|
+
date: 2025-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: deep_merge
|
@@ -41,6 +41,7 @@ executables:
|
|
41
41
|
- openapi-addparameters
|
42
42
|
- openapi-addresponses
|
43
43
|
- openapi-addschemas
|
44
|
+
- openapi-addsecurityschemes
|
44
45
|
- openapi-checkschemas
|
45
46
|
- openapi-frequencies
|
46
47
|
- openapi-generate
|
@@ -55,6 +56,7 @@ files:
|
|
55
56
|
- bin/openapi-addparameters
|
56
57
|
- bin/openapi-addresponses
|
57
58
|
- bin/openapi-addschemas
|
59
|
+
- bin/openapi-addsecurityschemes
|
58
60
|
- bin/openapi-checkschemas
|
59
61
|
- bin/openapi-frequencies
|
60
62
|
- bin/openapi-generate
|
@@ -71,7 +73,6 @@ files:
|
|
71
73
|
- lib/openapi/sourcetools/helper.rb
|
72
74
|
- lib/openapi/sourcetools/loaders.rb
|
73
75
|
- lib/openapi/sourcetools/output.rb
|
74
|
-
- lib/openapi/sourcetools/securityschemes.rb
|
75
76
|
- lib/openapi/sourcetools/task.rb
|
76
77
|
- lib/openapi/sourcetools/version.rb
|
77
78
|
homepage: https://xn--ismo-krkkinen-gfbd.fi/openapi-sourcetools/index.html
|
@@ -1,268 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
# Copyright © 2024-2025 Ismo Kärkkäinen
|
4
|
-
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
-
|
6
|
-
require_relative 'task'
|
7
|
-
require 'yaml'
|
8
|
-
|
9
|
-
|
10
|
-
module OpenAPISourceTools
|
11
|
-
# Class that contains security scheme objects and what headers
|
12
|
-
# and parameters are added to the request when the scheme is used.
|
13
|
-
class SecuritySchemeInfo
|
14
|
-
include Comparable
|
15
|
-
|
16
|
-
attr_reader :headers, :parameters, :query_parameters, :cookies
|
17
|
-
|
18
|
-
def initialize(security_scheme = {}, scheme_templates = [])
|
19
|
-
@headers = {}
|
20
|
-
@parameters = {}
|
21
|
-
@query_parameters = {}
|
22
|
-
@cookies = {}
|
23
|
-
scheme_templates.each do |template|
|
24
|
-
s = template['scheme']
|
25
|
-
match = true
|
26
|
-
s.each do |k, v|
|
27
|
-
unless v == security_scheme[k]
|
28
|
-
match = false
|
29
|
-
break
|
30
|
-
end
|
31
|
-
end
|
32
|
-
next unless match
|
33
|
-
o = template['output']
|
34
|
-
fill_in(@headers, o['headers'] || {}, security_scheme)
|
35
|
-
fill_in(@parameters, o['parameters'] || {}, security_scheme)
|
36
|
-
fill_in(@query_parameters, o['query_parameters'] || {}, security_scheme)
|
37
|
-
fill_in(@cookies, o['cookies'] || {}, security_scheme)
|
38
|
-
break
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def fill_in(output, source, scheme)
|
43
|
-
source.each do |k, v|
|
44
|
-
if k.start_with?('<') && k.end_with?('>')
|
45
|
-
scheme_key = k[1..-2]
|
46
|
-
scheme_value = scheme[scheme_key]
|
47
|
-
raise "Missing security scheme value for #{scheme_key}" if scheme_value.nil?
|
48
|
-
output[scheme_value] = v
|
49
|
-
else
|
50
|
-
output[k] = v
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def merge!(other)
|
56
|
-
@headers.merge!(other.headers)
|
57
|
-
@parameters.merge!(other.parameters)
|
58
|
-
@query_parameters.merge!(other.query_parameters)
|
59
|
-
@cookies.merge!(other.cookies)
|
60
|
-
end
|
61
|
-
|
62
|
-
def merge(other)
|
63
|
-
out = SecuritySchemeInfo.new({}, [])
|
64
|
-
out.merge!(self)
|
65
|
-
out.merge!(other)
|
66
|
-
out
|
67
|
-
end
|
68
|
-
|
69
|
-
def <=>(other)
|
70
|
-
# Only really interested in equality.
|
71
|
-
@headers <=> other.headers || @parameters <=> other.parameters || @query_parameters <=> other.query_parameters || @cookies <=> other.cookies
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
class ScopedSecuritySchemeInfo
|
76
|
-
include Comparable
|
77
|
-
|
78
|
-
attr_reader :ssi, :scopes
|
79
|
-
|
80
|
-
def initialize(ssi, scopes)
|
81
|
-
@ssi = ssi
|
82
|
-
@scopes = scopes
|
83
|
-
end
|
84
|
-
|
85
|
-
def merge(other)
|
86
|
-
@ssi = @ssi.merge(other.ssi)
|
87
|
-
@scopes = @scopes.concat(other.scopes).uniq
|
88
|
-
end
|
89
|
-
|
90
|
-
def <=>(other)
|
91
|
-
# Only really interested in equality.
|
92
|
-
@ssi <=> other.ssi || @scopes <=> other.scopes
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
# Helper class for dealing with securitySchemes.
|
97
|
-
# Adds :security key to each operation object and to root.
|
98
|
-
# They contain the used securitySchemes in use and the security schemes
|
99
|
-
# in effect for that operation.
|
100
|
-
class SecuritySchemesTask
|
101
|
-
include TaskInterface
|
102
|
-
|
103
|
-
def default_configuration
|
104
|
-
# type apiKey, name, in.
|
105
|
-
# type http, scheme, bearerFormat.
|
106
|
-
# type mutualTLS, beyond the scope of code templates.
|
107
|
-
# type oauth2, flows object.
|
108
|
-
# type openIdConnect, openIdConnectUrl. More for login?
|
109
|
-
YAML.safe_load(%q(---
|
110
|
-
security_schemes:
|
111
|
-
- scheme:
|
112
|
-
type: apiKey
|
113
|
-
in: header
|
114
|
-
output:
|
115
|
-
headers:
|
116
|
-
'<name>': string
|
117
|
-
- scheme:
|
118
|
-
type: apiKey
|
119
|
-
in: query
|
120
|
-
output:
|
121
|
-
parameters:
|
122
|
-
'<name>': string
|
123
|
-
- scheme:
|
124
|
-
type: apiKey
|
125
|
-
in: query
|
126
|
-
output:
|
127
|
-
query_parameters:
|
128
|
-
'<name>': string
|
129
|
-
- scheme:
|
130
|
-
type: apiKey
|
131
|
-
in: cookie
|
132
|
-
output:
|
133
|
-
cookies:
|
134
|
-
'<name>': true
|
135
|
-
- scheme:
|
136
|
-
type: http
|
137
|
-
scheme: basic
|
138
|
-
output:
|
139
|
-
headers:
|
140
|
-
Authorization: string
|
141
|
-
- scheme:
|
142
|
-
type: http
|
143
|
-
scheme: bearer
|
144
|
-
output:
|
145
|
-
headers:
|
146
|
-
Authorization: string
|
147
|
-
- scheme:
|
148
|
-
type: mutualTLS
|
149
|
-
output: {}
|
150
|
-
- scheme:
|
151
|
-
type: oauth2
|
152
|
-
output:
|
153
|
-
headers:
|
154
|
-
Authorization: string
|
155
|
-
- scheme:
|
156
|
-
type: openIdConnect
|
157
|
-
output:
|
158
|
-
headers:
|
159
|
-
Authorization: string
|
160
|
-
))
|
161
|
-
end
|
162
|
-
|
163
|
-
def convert_security_schemes(doc)
|
164
|
-
ss = doc.dig('components', 'securitySchemes')
|
165
|
-
return nil if ss.nil?
|
166
|
-
# Should create unique objects. Different name may lead to same object.
|
167
|
-
out = {}
|
168
|
-
ss.each do |name, security_scheme|
|
169
|
-
out[name] = SecuritySchemeInfo.new(security_scheme)
|
170
|
-
end
|
171
|
-
out
|
172
|
-
end
|
173
|
-
|
174
|
-
def operation_object_security(doc, schemes)
|
175
|
-
# Find all operation objects and security in effect.
|
176
|
-
all_ops = []
|
177
|
-
seen_secs = Set.new
|
178
|
-
root_sec = doc['security'] || []
|
179
|
-
not_method = %w[parameters servers summary description]
|
180
|
-
doc['paths'].each_value do |path_item|
|
181
|
-
path_sec = path_item['security'] || root_sec
|
182
|
-
path_item.each do |method, op|
|
183
|
-
next if not_method.include?(method)
|
184
|
-
op_sec = op['security'] || path_sec
|
185
|
-
all_ops.push([ op, op_sec ])
|
186
|
-
op_sec.each do |security_requirement|
|
187
|
-
names = security_requirement.keys
|
188
|
-
seen_secs.merge(names)
|
189
|
-
if names.empty? && !schemes.key?('')
|
190
|
-
schemes[''] = SecuritySchemeInfo.new
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
end
|
195
|
-
# Check that all seen secs names have a scheme. Report all in one place.
|
196
|
-
missing = false
|
197
|
-
seen_secs.to_a.sort!.each do |name|
|
198
|
-
unless schemes.key?(name)
|
199
|
-
missing = true
|
200
|
-
warn("#/components/securitySchemes is missing: #{name}")
|
201
|
-
end
|
202
|
-
end
|
203
|
-
return 1 if missing
|
204
|
-
# Now we know all individual parts are available.
|
205
|
-
# Map security arrays of objects to arrays of arrays.
|
206
|
-
all_scopeds = [] # For having just one instance for unique data combination.
|
207
|
-
all_ops.each do |pair|
|
208
|
-
sec = pair.second.map do |sec_req|
|
209
|
-
keys = sec_req.keys.sort!
|
210
|
-
values = keys.map { |name| sec_req[name].sort! }
|
211
|
-
if keys.empty?
|
212
|
-
keys = [ '' ]
|
213
|
-
values = [ [] ]
|
214
|
-
end
|
215
|
-
s3is = []
|
216
|
-
keys.size.times do |idx|
|
217
|
-
name = keys[idx]
|
218
|
-
scopes = values[idx]
|
219
|
-
s3i = ScopedSecuritySchemeInfo.new(schemes[name], scopes)
|
220
|
-
idx = all_scopeds.index(s3i)
|
221
|
-
if idx.nil?
|
222
|
-
all_scopeds.push(s3i)
|
223
|
-
else
|
224
|
-
s3i = all_scopeds(idx) # Use the same instance everywhere.
|
225
|
-
end
|
226
|
-
s3is.push(s3i)
|
227
|
-
end
|
228
|
-
s3is
|
229
|
-
end
|
230
|
-
pair.first[:security] = sec # Arrays of ScopedSecuritySchemeInfo.
|
231
|
-
# When individual objects are not needed, provide merged together items.
|
232
|
-
all_merged = []
|
233
|
-
pair.first[:security_merged] = sec.map do |s3is| # ScopedSecuritySchemeInfos.
|
234
|
-
m = s3is.first
|
235
|
-
s3is[1..].each do |s3i|
|
236
|
-
m = m.merge(s3i)
|
237
|
-
end
|
238
|
-
idx = all_merged.index(m)
|
239
|
-
if idx.nil?
|
240
|
-
all_merged.push(m)
|
241
|
-
else
|
242
|
-
m = all_merged[idx] # Use the same instance everywhere.
|
243
|
-
end
|
244
|
-
m
|
245
|
-
end
|
246
|
-
end
|
247
|
-
all_merged.map(&:ssi).uniq.sort!
|
248
|
-
end
|
249
|
-
|
250
|
-
def generate(_context_binding)
|
251
|
-
# Get security_schemes from config, append defaults to it.
|
252
|
-
scheme_templates = gen.configuration['security_schemes'] || []
|
253
|
-
scheme_templates.concat(default_configuration['security_schemes'])
|
254
|
-
simple_schemes = convert_security_schemes(Gen.doc)
|
255
|
-
# For all operation objects, add :security array with all used schemes.
|
256
|
-
merged_schemes = operation_object_security(Gen.doc, simple_schemes || {})
|
257
|
-
return merged_schemes if merged_schemes.is_a?(Integer)
|
258
|
-
Gen.doc[:securitySchemes] = simple_schemes unless simple_schemes.nil?
|
259
|
-
Gen.doc[:securitySchemes_merged] = merged_schemes unless merged_schemes.empty?
|
260
|
-
end
|
261
|
-
|
262
|
-
def discard
|
263
|
-
true
|
264
|
-
end
|
265
|
-
|
266
|
-
COMPONENTS = '#/components/'
|
267
|
-
end
|
268
|
-
end
|