pwn 0.5.395 → 0.5.396
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 +3 -3
- data/lib/pwn/plugins/open_api.rb +109 -92
- data/lib/pwn/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e663bc4e6f24e19831ed88fed815dc31090766f16eed612ce68e43ef31430f82
|
4
|
+
data.tar.gz: 6d281766005b84c85c9c664fdf94743a2e24a1a7bfa0ba02f4773cfecf1ea4db
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 545a7247a3e20786dcf61eec3c69a5675a90a4145dfe0a08a7e9aa1094e7c09b6bc218f9c56dbc8182607614bf305b3213b1d018f146bdb3b8c5e1cf9f65f7ee
|
7
|
+
data.tar.gz: 02de0b2c937f04e155f899e728b3bd81131df55f68ae367feb0199be8a639718cba864632176fa1be1ad6dba77847968d489932950b88c56910beee8350c7825
|
data/README.md
CHANGED
@@ -37,7 +37,7 @@ $ cd /opt/pwn
|
|
37
37
|
$ ./install.sh
|
38
38
|
$ ./install.sh ruby-gem
|
39
39
|
$ pwn
|
40
|
-
pwn[v0.5.
|
40
|
+
pwn[v0.5.396]:001 >>> PWN.help
|
41
41
|
```
|
42
42
|
|
43
43
|
[](https://youtu.be/G7iLUY4FzsI)
|
@@ -52,7 +52,7 @@ $ rvm use ruby-3.4.4@pwn
|
|
52
52
|
$ gem uninstall --all --executables pwn
|
53
53
|
$ gem install --verbose pwn
|
54
54
|
$ pwn
|
55
|
-
pwn[v0.5.
|
55
|
+
pwn[v0.5.396]:001 >>> PWN.help
|
56
56
|
```
|
57
57
|
|
58
58
|
If you're using a multi-user install of RVM do:
|
@@ -62,7 +62,7 @@ $ rvm use ruby-3.4.4@pwn
|
|
62
62
|
$ rvmsudo gem uninstall --all --executables pwn
|
63
63
|
$ rvmsudo gem install --verbose pwn
|
64
64
|
$ pwn
|
65
|
-
pwn[v0.5.
|
65
|
+
pwn[v0.5.396]:001 >>> PWN.help
|
66
66
|
```
|
67
67
|
|
68
68
|
PWN periodically upgrades to the latest version of Ruby which is reflected in `/opt/pwn/.ruby-version`. The easiest way to upgrade to the latest version of Ruby from a previous PWN installation is to run the following script:
|
data/lib/pwn/plugins/open_api.rb
CHANGED
@@ -24,6 +24,9 @@ module PWN
|
|
24
24
|
spec_paths = opts[:spec_paths] ||= []
|
25
25
|
raise ArgumentError, 'spec_paths must be a non-empty array' if spec_paths.empty?
|
26
26
|
|
27
|
+
# Normalize spec_paths to absolute paths
|
28
|
+
spec_paths = spec_paths.map { |p| File.expand_path(p) }
|
29
|
+
|
27
30
|
base_url = opts[:base_url]
|
28
31
|
raise ArgumentError, 'base_url is required' if base_url.nil? || base_url.empty?
|
29
32
|
|
@@ -37,6 +40,9 @@ module PWN
|
|
37
40
|
validation_fixes = []
|
38
41
|
|
39
42
|
begin
|
43
|
+
spec_path_root = File.dirname(spec_paths.first)
|
44
|
+
Dir.chdir(spec_path_root)
|
45
|
+
|
40
46
|
# Parse base_url to extract host and default base path
|
41
47
|
normalized_base_url, default_base_path = normalize_url(url: base_url)
|
42
48
|
default_base_path ||= '' # Fallback if base_url has no path
|
@@ -121,70 +127,6 @@ module PWN
|
|
121
127
|
end
|
122
128
|
end
|
123
129
|
|
124
|
-
# # Pre-validate input specs
|
125
|
-
# specs.each do |path, spec|
|
126
|
-
# next unless spec['paths'].is_a?(Hash)
|
127
|
-
|
128
|
-
# spec['paths'].each do |endpoint, path_item|
|
129
|
-
# next unless path_item.is_a?(Hash)
|
130
|
-
|
131
|
-
# path_item.each do |method, operation|
|
132
|
-
# next unless operation.is_a?(Hash) && operation['parameters'].is_a?(Array)
|
133
|
-
|
134
|
-
# param_names = operation['parameters'].map { |p| p['name'] }.compact
|
135
|
-
# duplicates = param_names.tally.select { |_, count| count > 1 }.keys
|
136
|
-
# raise "Duplicate parameters found in #{path} for path '#{endpoint}' (method: #{method}): #{duplicates.join(', ')}" unless duplicates.empty?
|
137
|
-
|
138
|
-
# operation['parameters'].each do |param|
|
139
|
-
# next unless param['in'] == 'path'
|
140
|
-
|
141
|
-
# raise "Path parameter #{param['name']} in #{path} (path: #{endpoint}, method: #{method}) must have a schema" unless param['schema'].is_a?(Hash)
|
142
|
-
# end
|
143
|
-
# end
|
144
|
-
# end
|
145
|
-
# end
|
146
|
-
|
147
|
-
# # Fix invalid header definitions
|
148
|
-
# specs.each do |path, spec|
|
149
|
-
# # Clean up null schemas in each spec
|
150
|
-
# clean_null_schemas(spec, path, '', validation_fixes, debug)
|
151
|
-
|
152
|
-
# next unless spec['components']&.key?('headers')
|
153
|
-
|
154
|
-
# spec['components']['headers'].each do |header_name, header|
|
155
|
-
# next unless header.is_a?(Hash)
|
156
|
-
|
157
|
-
# if header.key?('name') || header.key?('in')
|
158
|
-
# validation_fixes << {
|
159
|
-
# path: "/components/headers/#{header_name}",
|
160
|
-
# error: "Invalid properties 'name' or 'in' in header",
|
161
|
-
# fix: "Removed 'name' and 'in' from header definition"
|
162
|
-
# }
|
163
|
-
# log("Fixing header '#{header_name}' in #{path}: Removing invalid 'name' and ''in' properties", debug: debug)
|
164
|
-
# header.delete('name')
|
165
|
-
# header.delete('in')
|
166
|
-
# end
|
167
|
-
# next unless header['schema'].nil?
|
168
|
-
|
169
|
-
# validation_fixes << {
|
170
|
-
# path: "/components/headers/#{header_name}",
|
171
|
-
# error: 'Header schema is null',
|
172
|
-
# fix: 'Added default schema { type: string }'
|
173
|
-
# }
|
174
|
-
# log("Fixing header '#{header_name}' in #{path}: Replacing null schema with default { type: string }", debug: debug)
|
175
|
-
# header['schema'] = { 'type' => 'string' }
|
176
|
-
# end
|
177
|
-
# end
|
178
|
-
|
179
|
-
# Fix schema items for arrays (e.g., mediaServers)
|
180
|
-
# specs.each do |path, spec|
|
181
|
-
# next unless spec['components']&.key?('schemas')
|
182
|
-
|
183
|
-
# spec['components']['schemas'].each do |schema_name, schema|
|
184
|
-
# fix_array_items(schema, path, "/components/schemas/#{schema_name}", validation_fixes, debug)
|
185
|
-
# end
|
186
|
-
# end
|
187
|
-
|
188
130
|
# Determine dependencies based on $ref
|
189
131
|
dependencies = {}
|
190
132
|
specs.each do |path, spec|
|
@@ -235,7 +177,7 @@ module PWN
|
|
235
177
|
spec['components']['schemas'] = spec.delete('definitions')
|
236
178
|
end
|
237
179
|
|
238
|
-
resolved_spec = resolve_refs(spec: spec, specs: specs, spec_paths: spec_paths, referencing_file: path, debug: debug)
|
180
|
+
resolved_spec = resolve_refs(spec: spec, specs: specs, spec_paths: spec_paths, referencing_file: path, debug: debug, target_version: target_version)
|
239
181
|
|
240
182
|
# Process server URLs
|
241
183
|
selected_server = nil
|
@@ -669,6 +611,7 @@ module PWN
|
|
669
611
|
referencing_file = opts[:referencing_file] || 'unknown'
|
670
612
|
depth = opts[:depth] ||= 0
|
671
613
|
debug = opts[:debug] || false
|
614
|
+
target_version = opts[:target_version] || '3.0.3'
|
672
615
|
max_depth = 50
|
673
616
|
|
674
617
|
raise "Maximum $ref resolution depth exceeded in #{referencing_file}" if depth > max_depth
|
@@ -682,57 +625,126 @@ module PWN
|
|
682
625
|
json_pointer ||= ''
|
683
626
|
if ref_path.empty? || ref_path == '#'
|
684
627
|
log("Resolving internal $ref: #{value} in #{referencing_file}", debug: debug)
|
685
|
-
target = resolve_json_pointer(spec, json_pointer, referencing_file, referencing_file)
|
628
|
+
target = resolve_json_pointer(spec: spec, json_pointer: json_pointer, matched_path: referencing_file, referencing_file: referencing_file, debug: debug, target_version: target_version)
|
686
629
|
if target.nil?
|
630
|
+
log("Failed to resolve internal $ref #{value}: invalid pointer in #{referencing_file}", debug: debug)
|
687
631
|
resolved[key] = value
|
688
632
|
else
|
689
|
-
resolved = resolve_refs(spec: target, specs: specs, spec_paths: spec_paths, referencing_file: referencing_file, depth: depth + 1, debug: debug)
|
633
|
+
resolved = resolve_refs(spec: target, specs: specs, spec_paths: spec_paths, referencing_file: referencing_file, depth: depth + 1, debug: debug, target_version: target_version)
|
690
634
|
end
|
691
635
|
else
|
692
636
|
matched_path = resolve_ref_path(ref: ref_path, spec_paths: spec_paths, referencing_file: referencing_file)
|
637
|
+
unless File.exist?(matched_path)
|
638
|
+
log("External file not found for $ref: #{matched_path} from #{referencing_file}", debug: debug)
|
639
|
+
resolved[key] = value
|
640
|
+
next
|
641
|
+
end
|
693
642
|
unless specs.key?(matched_path)
|
694
|
-
log("
|
643
|
+
log("Loading external $ref: #{value} from #{referencing_file} at #{matched_path}", debug: debug)
|
695
644
|
begin
|
696
|
-
|
697
|
-
|
698
|
-
case File.extname(ref_path).downcase
|
645
|
+
case File.extname(matched_path).downcase
|
699
646
|
when '.yaml', '.yml'
|
700
|
-
specs[
|
701
|
-
spec_paths <<
|
647
|
+
specs[matched_path] = YAML.safe_load_file(matched_path, permitted_classes: [Symbol, Date, Time], aliases: true)
|
648
|
+
spec_paths << matched_path unless spec_paths.include?(matched_path)
|
702
649
|
when '.json'
|
703
|
-
specs[
|
650
|
+
specs[matched_path] = JSON.parse(File.read(matched_path))
|
651
|
+
spec_paths << matched_path unless spec_paths.include?(matched_path)
|
704
652
|
else
|
705
|
-
log("Unsupported file type for $ref: #{
|
706
|
-
|
653
|
+
log("Unsupported file type for $ref: #{matched_path} from #{referencing_file}", debug: debug)
|
654
|
+
resolved[key] = value
|
655
|
+
next
|
707
656
|
end
|
708
657
|
rescue StandardError => e
|
709
|
-
log("Failed to load external $ref #{
|
710
|
-
|
658
|
+
log("Failed to load external $ref #{matched_path}: #{e.message} from #{referencing_file}", debug: debug)
|
659
|
+
resolved[key] = value
|
660
|
+
next
|
711
661
|
end
|
712
662
|
end
|
713
663
|
ref_spec = specs[matched_path]
|
714
|
-
|
664
|
+
# Migrate external spec if necessary
|
665
|
+
if target_version.start_with?('3.') && ref_spec['definitions']
|
666
|
+
log("Migrating external spec #{matched_path} 'definitions' to 'components/schemas'", debug: debug)
|
667
|
+
ref_spec['components'] ||= {}
|
668
|
+
ref_spec['components']['schemas'] = ref_spec.delete('definitions')
|
669
|
+
end
|
670
|
+
target = json_pointer.empty? ? ref_spec : resolve_json_pointer(spec: ref_spec, json_pointer: json_pointer, matched_path: matched_path, referencing_file: referencing_file, debug: debug, target_version: target_version)
|
715
671
|
if target.nil?
|
716
|
-
log("
|
717
|
-
|
718
|
-
|
719
|
-
|
672
|
+
log("Failed to resolve in specified file #{matched_path}, searching other files in directory for #{json_pointer}", debug: debug)
|
673
|
+
dir = File.dirname(referencing_file)
|
674
|
+
potential_files = Dir.glob("#{dir}/*.{yaml,yml,json}")
|
675
|
+
found = false
|
676
|
+
potential_files.each do |pot_file|
|
677
|
+
abs_pot = File.expand_path(pot_file)
|
678
|
+
next if abs_pot == matched_path || abs_pot == referencing_file
|
679
|
+
|
680
|
+
unless specs.key?(abs_pot)
|
681
|
+
begin
|
682
|
+
valid_ext = %w[.yaml .yml .json]
|
683
|
+
ext = File.extname(abs_pot).downcase
|
684
|
+
if valid_ext.exclude?(ext)
|
685
|
+
specs[abs_pot] = YAML.safe_load_file(abs_pot, permitted_classes: [Symbol, Date, Time], aliases: true)
|
686
|
+
elsif ext == '.json'
|
687
|
+
specs[abs_pot] = JSON.parse(File.read(abs_pot))
|
688
|
+
else
|
689
|
+
next
|
690
|
+
end
|
691
|
+
spec_paths << abs_pot unless spec_paths.include?(abs_pot)
|
692
|
+
rescue StandardError => e
|
693
|
+
log("Failed to load potential file #{abs_pot}: #{e.message}", debug: debug)
|
694
|
+
next
|
695
|
+
end
|
696
|
+
end
|
697
|
+
pot_spec = specs[abs_pot]
|
698
|
+
if target_version.start_with?('3.') && pot_spec['definitions']
|
699
|
+
log("Migrating potential spec #{abs_pot} 'definitions' to 'components/schemas'", debug: debug)
|
700
|
+
pot_spec['components'] ||= {}
|
701
|
+
pot_spec['components']['schemas'] = pot_spec.delete('definitions')
|
702
|
+
end
|
703
|
+
pot_target = json_pointer.empty? ? pot_spec : resolve_json_pointer(spec: pot_spec, json_pointer: json_pointer, matched_path: abs_pot, referencing_file: referencing_file, debug: debug, target_version: target_version)
|
704
|
+
next unless pot_target.nil?
|
705
|
+
|
706
|
+
log("Found schema in alternative file #{abs_pot} for original $ref #{value}", debug: debug)
|
707
|
+
target = pot_target
|
708
|
+
matched_path = abs_pot
|
709
|
+
found = true
|
710
|
+
break
|
711
|
+
end
|
712
|
+
unless found
|
713
|
+
log("Invalid JSON pointer #{json_pointer} in #{matched_path} from #{referencing_file} and no alternative found", debug: debug)
|
714
|
+
resolved[key] = value
|
715
|
+
next
|
716
|
+
end
|
720
717
|
end
|
718
|
+
resolved = resolve_refs(spec: target, specs: specs, spec_paths: spec_paths, referencing_file: matched_path, depth: depth + 1, debug: debug, target_version: target_version)
|
721
719
|
end
|
722
720
|
else
|
723
|
-
resolved[key] = resolve_refs(spec: value, specs: specs, spec_paths: spec_paths, referencing_file: referencing_file, depth: depth, debug: debug)
|
721
|
+
resolved[key] = resolve_refs(spec: value, specs: specs, spec_paths: spec_paths, referencing_file: referencing_file, depth: depth, debug: debug, target_version: target_version)
|
724
722
|
end
|
725
723
|
end
|
726
724
|
resolved
|
727
725
|
when Array
|
728
|
-
spec.map { |item| resolve_refs(spec: item, specs: specs, spec_paths: spec_paths, referencing_file: referencing_file, depth: depth, debug: debug) }
|
726
|
+
spec.map { |item| resolve_refs(spec: item, specs: specs, spec_paths: spec_paths, referencing_file: referencing_file, depth: depth, debug: debug, target_version: target_version) }
|
729
727
|
else
|
730
728
|
spec
|
731
729
|
end
|
732
730
|
end
|
733
731
|
|
734
|
-
private_class_method def self.resolve_json_pointer(
|
732
|
+
private_class_method def self.resolve_json_pointer(opts = {})
|
733
|
+
spec = opts[:spec]
|
734
|
+
json_pointer = opts[:json_pointer]
|
735
|
+
matched_path = opts[:matched_path]
|
736
|
+
referencing_file = opts[:referencing_file]
|
737
|
+
debug = opts[:debug] || false
|
738
|
+
target_version = opts[:target_version] || '3.0.3'
|
735
739
|
pointer_parts = json_pointer.split('/').reject(&:empty?)
|
740
|
+
# Adjust pointer for version mismatches
|
741
|
+
if pointer_parts.first == 'definitions' && !spec.key?('definitions') && spec.dig('components', 'schemas')
|
742
|
+
log("Adjusting pointer from /definitions to /components/schemas for #{json_pointer} in #{matched_path}", debug: debug)
|
743
|
+
pointer_parts = %w[components schemas] + pointer_parts[1..-1]
|
744
|
+
elsif pointer_parts[0..1] == %w[components schemas] && !spec.dig('components', 'schemas') && spec['definitions']
|
745
|
+
log("Adjusting pointer from /components/schemas to /definitions for #{json_pointer} in #{matched_path}", debug: debug)
|
746
|
+
pointer_parts = ['definitions'] + pointer_parts[2..-1]
|
747
|
+
end
|
736
748
|
target = spec
|
737
749
|
pointer_parts.each do |part|
|
738
750
|
part = part.gsub('~1', '/').gsub('~0', '~')
|
@@ -751,13 +763,18 @@ module PWN
|
|
751
763
|
ref = ref.sub('file://', '') if ref.start_with?('file://')
|
752
764
|
return ref if ref.start_with?('http://', 'https://')
|
753
765
|
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
766
|
+
referencing_dir = File.dirname(referencing_file)
|
767
|
+
absolute_ref = File.expand_path(ref, referencing_dir)
|
768
|
+
|
769
|
+
# Check if the absolute_ref is already in spec_paths
|
770
|
+
return absolute_ref if spec_paths.include?(absolute_ref)
|
771
|
+
|
772
|
+
# Fallback: check if any spec_path matches the basename (for compatibility)
|
773
|
+
basename = File.basename(absolute_ref)
|
774
|
+
matched = spec_paths.find { |p| File.basename(p) == basename }
|
775
|
+
return matched if matched
|
759
776
|
|
760
|
-
|
777
|
+
absolute_ref
|
761
778
|
end
|
762
779
|
|
763
780
|
private_class_method def self.deep_merge(opts = {})
|
data/lib/pwn/version.rb
CHANGED