Skip to content

Commit

Permalink
Merge pull request #28 from hahwul/main
Browse files Browse the repository at this point in the history
Sync
  • Loading branch information
hahwul authored Aug 19, 2023
2 parents ad37eec + 04e8181 commit 238ecc6
Show file tree
Hide file tree
Showing 15 changed files with 327 additions and 61 deletions.
2 changes: 1 addition & 1 deletion .ameba.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Metrics/CyclomaticComplexity:
Description: Disallows methods with a cyclomatic complexity higher than `MaxComplexity`
MaxComplexity: 25
MaxComplexity: 30
Enabled: true
Severity: Warning
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@
| JS | Express ||| X | X | X |
| JS | Next | X | X | X | X | X |

### Specification

| Specification | Format | URL | Method | Param | Header | WS |
|------------------------|---------|-----|--------|-------|--------|----|
| OAS 2.0 (Swagger 2.0) | JSON ||||| X |
| OAS 2.0 (Swagger 2.0) | YAML ||||| X |
| OAS 3.0 | JSON ||||| X |
| OAS 3.0 | YAML ||||| X |

## Installation
### Homebrew (macOS)
```bash
Expand Down
2 changes: 1 addition & 1 deletion shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: noir
version: 0.3.0
version: 0.4.0

authors:
- hahwul <[email protected]>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require "../../src/detector/detectors/*"
require "../../src/models/code_locator"

describe "Detect Swagger Docs" do
describe "Detect OAS 2.0(Swagger) Docs" do
options = default_options()
instance = DetectorSwagger.new options
instance = DetectorOas2.new options

it "json format" do
content = <<-EOS
Expand Down
40 changes: 40 additions & 0 deletions spec/detector/detect_oas3_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
require "../../src/detector/detectors/*"
require "../../src/models/code_locator"

describe "Detect OAS 3.0 Docs" do
options = default_options()
instance = DetectorOas3.new options

it "json format" do
content = <<-EOS
{
"openapi": "3.0.0",
"info": "test"
}
EOS

instance.detect("docs.json", content).should eq(true)
end
it "yaml format" do
content = <<-EOS
openapi: 3.0.0
info:
version: 1.0.0
EOS

instance.detect("docs.yml", content).should eq(true)
end

it "code_locator" do
content = <<-EOS
{
"openapi": "3.0.0",
"info": "test"
}
EOS

instance.detect("docs.json", content)
locator = CodeLocator.instance
locator.get("oas3-json").should eq("docs.json")
end
end
3 changes: 2 additions & 1 deletion src/analyzer/analyzer.cr
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ def initialize_analyzers(logger : NoirLogger)
analyzers["python_django"] = ->analyzer_django(Hash(Symbol, String))
analyzers["js_express"] = ->analyzer_express(Hash(Symbol, String))
analyzers["crystal_kemal"] = ->analyzer_kemal(Hash(Symbol, String))
analyzers["swagger"] = ->analyzer_swagger(Hash(Symbol, String))
analyzers["oas2"] = ->analyzer_oas2(Hash(Symbol, String))
analyzers["oas3"] = ->analyzer_oas3(Hash(Symbol, String))

logger.info_sub "#{analyzers.size} Analyzers initialized"
logger.debug "Analyzers:"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require "../../models/analyzer"

class AnalyzerSwagger < Analyzer
class AnalyzerOAS2 < Analyzer
def analyze
locator = CodeLocator.instance
swagger_json = locator.get("swagger-json")
Expand All @@ -10,28 +10,34 @@ class AnalyzerSwagger < Analyzer
if File.exists?(swagger_json)
content = File.read(swagger_json, encoding: "utf-8", invalid: :skip)
json_obj = JSON.parse(content)
base_path = @url
if json_obj["basePath"].to_s != ""
base_path = base_path + json_obj["basePath"].to_s
end
json_obj["paths"].as_h.each do |path, path_obj|
path_obj.as_h.each do |method, method_obj|
params_query = [] of Param
params_body = [] of Param
params = [] of Param

if method_obj.as_h.has_key?("parameters")
method_obj["parameters"].as_a.each do |param_obj|
param_name = param_obj["name"].to_s
if param_obj["in"] == "query"
param = Param.new(param_name, "", "query")
params_query << param
params << param
elsif param_obj["in"] == "body"
param = Param.new(param_name, "", "json")
params_body << param
params << param
elsif param_obj["in"] == "formData"
param = Param.new(param_name, "", "form")
params_body << param
params << param
elsif param_obj["in"] == "header"
param = Param.new(param_name, "", "header")
params << param
end
end
@result << Endpoint.new(path, method.upcase, params_body)
@result << Endpoint.new(base_path + path, method.upcase, params)
else
@result << Endpoint.new(path, method.upcase)
@result << Endpoint.new(base_path + path, method.upcase)
end
end
end
Expand All @@ -42,28 +48,34 @@ class AnalyzerSwagger < Analyzer
if File.exists?(swagger_yaml)
content = File.read(swagger_yaml, encoding: "utf-8", invalid: :skip)
yaml_obj = YAML.parse(content)
base_path = @url
if yaml_obj["basePath"].to_s != ""
base_path = base_path + yaml_obj["basePath"].to_s
end
yaml_obj["paths"].as_h.each do |path, path_obj|
path_obj.as_h.each do |method, method_obj|
params_query = [] of Param
params_body = [] of Param
params = [] of Param

if method_obj.as_h.has_key?("parameters")
method_obj["parameters"].as_a.each do |param_obj|
param_name = param_obj["name"].to_s
if param_obj["in"] == "query"
param = Param.new(param_name, "", "query")
params_query << param
params << param
elsif param_obj["in"] == "body"
param = Param.new(param_name, "", "json")
params_body << param
params << param
elsif param_obj["in"] == "formData"
param = Param.new(param_name, "", "form")
params_body << param
params << param
elsif param_obj["in"] == "header"
param = Param.new(param_name, "", "header")
params << param
end
end
@result << Endpoint.new(path.to_s, method.to_s.upcase, params_body)
@result << Endpoint.new(base_path + path.to_s, method.to_s.upcase, params)
else
@result << Endpoint.new(path.to_s, method.to_s.upcase)
@result << Endpoint.new(base_path + path.to_s, method.to_s.upcase)
end
end
end
Expand All @@ -74,7 +86,7 @@ class AnalyzerSwagger < Analyzer
end
end

def analyzer_swagger(options : Hash(Symbol, String))
instance = AnalyzerSwagger.new(options)
def analyzer_oas2(options : Hash(Symbol, String))
instance = AnalyzerOAS2.new(options)
instance.analyze
end
143 changes: 143 additions & 0 deletions src/analyzer/analyzers/analyzer_oas3.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
require "../../models/analyzer"

class AnalyzerOAS3 < Analyzer
def get_base_path(servers)
base_path = @url
servers.as_a.each do |server_obj|
if server_obj["url"].to_s.starts_with?("http")
user_uri = URI.parse(@url)
source_uri = URI.parse(server_obj["url"].to_s)
if user_uri.host == source_uri.host
base_path = @url + source_uri.path
break
end
end
end

base_path
end

def analyze
locator = CodeLocator.instance
oas3_json = locator.get("oas3-json")
oas3_yaml = locator.get("oas3-yaml")

if !oas3_json.nil?
if File.exists?(oas3_json)
content = File.read(oas3_json, encoding: "utf-8", invalid: :skip)
json_obj = JSON.parse(content)

base_path = get_base_path json_obj["servers"]

json_obj["paths"].as_h.each do |path, path_obj|
path_obj.as_h.each do |method, method_obj|
params = [] of Param

if method_obj.is_a?(JSON::Any) && method_obj.is_a?(Hash(String, JSON::Any))
if method_obj.as_h.has_key?("parameters")
method_obj["parameters"].as_a.each do |param_obj|
param_name = param_obj["name"].to_s
if param_obj["in"] == "query"
param = Param.new(param_name, "", "query")
params << param
elsif param_obj["in"] == "header"
param = Param.new(param_name, "", "header")
params << param
end
end
end

if method_obj.as_h.has_key?("requestBody")
method_obj["requestBody"]["content"].as_h.each do |content_type, content_obj|
if content_type == "application/json"
content_obj["schema"]["properties"].as_h.each do |param_name, _|
param = Param.new(param_name, "", "json")
params << param
end
elsif content_type == "application/x-www-form-urlencoded"
content_obj["schema"]["properties"].as_h.each do |param_name, _|
param = Param.new(param_name, "", "form")
params << param
end
end
end
end
end

if params.size > 0 && params.size > 0
@result << Endpoint.new(base_path + path, method.upcase, params + params)
elsif params.size > 0
@result << Endpoint.new(base_path + path, method.upcase, params)
elsif params.size > 0
@result << Endpoint.new(base_path + path, method.upcase, params)
else
@result << Endpoint.new(base_path + path, method.upcase)
end
end
end
end
end

if !oas3_yaml.nil?
if File.exists?(oas3_yaml)
content = File.read(oas3_yaml, encoding: "utf-8", invalid: :skip)
yaml_obj = YAML.parse(content)
base_path = get_base_path yaml_obj["servers"]

yaml_obj["paths"].as_h.each do |path, path_obj|
path_obj.as_h.each do |method, method_obj|
params = [] of Param

if method_obj.is_a?(YAML::Any) && method_obj.is_a?(Hash(String, YAML::Any))
if method_obj.as_h.has_key?("parameters")
method_obj["parameters"].as_a.each do |param_obj|
param_name = param_obj["name"].to_s
if param_obj["in"] == "query"
param = Param.new(param_name, "", "query")
params << param
elsif param_obj["in"] == "header"
param = Param.new(param_name, "", "header")
params << param
end
end
end

if method_obj.as_h.has_key?("requestBody")
method_obj["requestBody"]["content"].as_h.each do |content_type, content_obj|
if content_type == "application/json"
content_obj["schema"]["properties"].as_h.each do |param_name, _|
param = Param.new(param_name.to_s, "", "json")
params << param
end
elsif content_type == "application/x-www-form-urlencoded"
content_obj["schema"]["properties"].as_h.each do |param_name, _|
param = Param.new(param_name.to_s, "", "form")
params << param
end
end
end
end
end

if params.size > 0 && params.size > 0
@result << Endpoint.new(base_path + path.to_s, method.to_s.upcase, params + params)
elsif params.size > 0
@result << Endpoint.new(base_path + path.to_s, method.to_s.upcase, params)
elsif params.size > 0
@result << Endpoint.new(base_path + path.to_s, method.to_s.upcase, params)
else
@result << Endpoint.new(base_path + path.to_s, method.to_s.upcase)
end
end
end
end
end

@result
end
end

def analyzer_oas3(options : Hash(Symbol, String))
instance = AnalyzerOAS3.new(options)
instance.analyze
end
2 changes: 1 addition & 1 deletion src/detector/detector.cr
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ def detect_techs(base_path : String, options : Hash(Symbol, String))
defind_detectors([
DetectorCrystalKemal, DetectorGoEcho, DetectorJavaJsp, DetectorJavaSpring,
DetectorJsExpress, DetectorPhpPure, DetectorPythonDjango, DetectorPythonFlask,
DetectorRubyRails, DetectorRubySinatra, DetectorSwagger,
DetectorRubyRails, DetectorRubySinatra, DetectorOas2, DetectorOas3,
])
Dir.glob("#{base_path}/**/*") do |file|
spawn do
Expand Down
37 changes: 37 additions & 0 deletions src/detector/detectors/oas2.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require "../../models/detector"
require "../../utils/json"
require "../../utils/yaml"
require "../../models/code_locator"

class DetectorOas2 < Detector
def detect(filename : String, file_contents : String) : Bool
check = false
if valid_json? file_contents
data = JSON.parse(file_contents)
begin
if data["swagger"].as_s.includes? "2."
check = true
locator = CodeLocator.instance
locator.set("swagger-json", filename)
end
rescue
end
elsif valid_yaml? file_contents
data = YAML.parse(file_contents)
begin
if data["swagger"].as_s.includes? "2."
check = true
locator = CodeLocator.instance
locator.set("swagger-yaml", filename)
end
rescue
end
end

check
end

def set_name
@name = "oas2"
end
end
Loading

0 comments on commit 238ecc6

Please sign in to comment.