Compare commits

..
7 Commits
11 changed files with 175 additions and 92 deletions

No files matched your search

+11
View File
@@ -1,3 +1,5 @@
version: 2
before:
hooks:
- go mod tidy
@@ -29,3 +31,12 @@ changelog:
exclude:
- "^docs:"
- "^test:"
groups:
- title: "New Features"
regexp: '^(Add|add)(s|ed|ing)?'
order: 0
- title: "Bug Fixes"
regexp: '^(Fix|fix)(es|ed|ing)?'
order: 1
- title: "Others"
order: 999
+1 -1
View File
@@ -32,7 +32,7 @@ $ go install github.com/BishopFox/sj@latest
![Automate Command](img/sj-automate.gif)
> Use the `prepare` command to prepare a list of curl commands for manual testing. You will likely have to modify these slightly.
> Use the `prepare` command to prepare a list of commands for manual testing. Currently supports both `curl` and `sqlmap`. You will likely have to modify these slightly.
![Prepare Command](img/sj-prepare.gif)
+60 -51
View File
@@ -21,61 +21,70 @@ var (
func CheckSecDefs(doc3 openapi3.T) (apiInQuery bool, apiKey string, apiKeyName string) {
if len(doc3.Components.SecuritySchemes) > 0 {
log.Info("Available authentication mechanisms: ")
}
for mechanism := range doc3.Components.SecuritySchemes {
if doc3.Components.SecuritySchemes[mechanism].Value == nil {
log.Error("Authorization schema structure unsupported. Manual testing is required.")
return false, "", ""
if outputFormat == "json" {
if len(doc3.Components.SecuritySchemes) != 0 {
log.Warnf("The following authentication mechanisms are supported. If necessary, supply these manually when using the JSON output format:\n")
}
if doc3.Components.SecuritySchemes[mechanism].Value.Scheme != "" {
fmt.Printf(" - %s (%s)\n", mechanism, doc3.Components.SecuritySchemes[mechanism].Value.Scheme)
} else {
fmt.Printf(" - %s\n", mechanism)
for mechanism := range doc3.Components.SecuritySchemes {
log.Infof(`Type: %s, Scheme: %s`, doc3.Components.SecuritySchemes[mechanism].Value.Type, doc3.Components.SecuritySchemes[mechanism].Value.Scheme)
}
} else {
if len(doc3.Components.SecuritySchemes) > 0 {
log.Info("Available authentication mechanisms: ")
}
if doc3.Components.SecuritySchemes[mechanism].Value.Type == "http" && !quiet {
if doc3.Components.SecuritySchemes[mechanism].Value.Scheme == "basic" {
log.Infof("Basic Authentication is accepted. Supply a username and password? (y/N)")
fmt.Scanln(&autoApplyBasicAuth)
autoApplyBasicAuth = strings.ToLower(autoApplyBasicAuth)
if autoApplyBasicAuth == "y" {
fmt.Printf("Enter a username.")
fmt.Scanln(&basicAuthUser)
fmt.Printf("Enter a password.")
fmt.Scanln(&basicAuthPass)
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
log.Infof("Using %s as the Basic Auth value.", basicAuthString)
Headers = append(Headers, "Authorization: Basic "+basicAuthString)
} else {
log.Warn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
for mechanism := range doc3.Components.SecuritySchemes {
if doc3.Components.SecuritySchemes[mechanism].Value == nil {
log.Error("Authorization schema structure unsupported. Manual testing is required.")
return false, "", ""
}
if doc3.Components.SecuritySchemes[mechanism].Value.Scheme != "" {
fmt.Printf(" - %s (%s)\n", mechanism, doc3.Components.SecuritySchemes[mechanism].Value.Scheme)
} else if outputFormat != "json" {
fmt.Printf(" - %s\n", mechanism)
}
if doc3.Components.SecuritySchemes[mechanism].Value.Type == "http" && !quiet {
if doc3.Components.SecuritySchemes[mechanism].Value.Scheme == "basic" {
log.Infof("Basic Authentication is accepted. Supply a username and password? (y/N)")
fmt.Scanln(&autoApplyBasicAuth)
autoApplyBasicAuth = strings.ToLower(autoApplyBasicAuth)
if autoApplyBasicAuth == "y" {
fmt.Printf("Enter a username.")
fmt.Scanln(&basicAuthUser)
fmt.Printf("Enter a password.")
fmt.Scanln(&basicAuthPass)
basicAuth = []byte(basicAuthUser + ":" + basicAuthPass)
basicAuthString = base64.StdEncoding.EncodeToString(basicAuth)
log.Infof("Using %s as the Basic Auth value.", basicAuthString)
Headers = append(Headers, "Authorization: Basic "+basicAuthString)
} else {
log.Warn("A basic authentication header is accepted. Review the spec and craft a header manually using the -H flag.")
}
} else if strings.ToLower(doc3.Components.SecuritySchemes[mechanism].Value.Scheme) == "bearer" {
log.Warn("A bearer token is accepted. Review the spec and craft a token manually using the -H flag.")
}
} else if doc3.Components.SecuritySchemes[mechanism].Value.Type == "apiKey" && doc3.Components.SecuritySchemes[mechanism].Value.In == "query" && !quiet {
apiInQuery = true
log.Infof("An API key can be provided via a parameter string. Would you like to apply one? (y/N)")
fmt.Scanln(&autoApplyAPIKey)
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
if autoApplyAPIKey == "y" {
apiKeyName = doc3.Components.SecuritySchemes[mechanism].Value.Name
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
fmt.Scanln(&apiKey)
log.Infof("Using %s=%s as the API key in all requests.", apiKeyName, apiKey)
}
} else if doc3.Components.SecuritySchemes[mechanism].Value.Type == "apiKey" && doc3.Components.SecuritySchemes[mechanism].Value.In == "header" && !quiet {
log.Infof("An API key can be provided via the header %s. Would you like to apply one? (y/N)", doc3.Components.SecuritySchemes[mechanism].Value.Name)
fmt.Scanln(&autoApplyAPIKey)
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
if autoApplyAPIKey == "y" {
apiKeyName = doc3.Components.SecuritySchemes[mechanism].Value.Name
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
fmt.Scanln(&apiKey)
Headers = append(Headers, doc3.Components.SecuritySchemes[mechanism].Value.Name+": "+apiKey)
}
} else if strings.ToLower(doc3.Components.SecuritySchemes[mechanism].Value.Scheme) == "bearer" {
log.Warn("A bearer token is accepted. Review the spec and craft a token manually using the -H flag.")
}
} else if doc3.Components.SecuritySchemes[mechanism].Value.Type == "apiKey" && doc3.Components.SecuritySchemes[mechanism].Value.In == "query" && !quiet {
apiInQuery = true
log.Infof("An API key can be provided via a parameter string. Would you like to apply one? (y/N)")
fmt.Scanln(&autoApplyAPIKey)
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
if autoApplyAPIKey == "y" {
apiKeyName = doc3.Components.SecuritySchemes[mechanism].Value.Name
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
fmt.Scanln(&apiKey)
log.Infof("Using %s=%s as the API key in all requests.", apiKeyName, apiKey)
}
} else if doc3.Components.SecuritySchemes[mechanism].Value.Type == "apiKey" && doc3.Components.SecuritySchemes[mechanism].Value.In == "header" && !quiet {
log.Infof("An API key can be provided via the header %s. Would you like to apply one? (y/N)", doc3.Components.SecuritySchemes[mechanism].Value.Name)
fmt.Scanln(&autoApplyAPIKey)
autoApplyAPIKey = strings.ToLower(autoApplyAPIKey)
if autoApplyAPIKey == "y" {
apiKeyName = doc3.Components.SecuritySchemes[mechanism].Value.Name
fmt.Printf("What value would you like to use for the API key (%s)?", apiKeyName)
fmt.Scanln(&apiKey)
Headers = append(Headers, doc3.Components.SecuritySchemes[mechanism].Value.Name+": "+apiKey)
}
}
}
+2 -2
View File
@@ -37,7 +37,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
}
if swaggerURL != "" {
bodyBytes, _, _ = MakeRequest(client, "GET", swaggerURL, timeout, nil, "automate")
bodyBytes, _, _ = MakeRequest(client, "GET", swaggerURL, timeout, nil)
} else {
specFile, err := os.Open(localFile)
if err != nil {
@@ -46,7 +46,7 @@ responds in an abnormal way, manual testing should be conducted (prepare manual
bodyBytes, _ = io.ReadAll(specFile)
}
GenerateRequests(bodyBytes, client, "automate")
GenerateRequests(bodyBytes, client)
},
}
+2 -2
View File
@@ -104,7 +104,7 @@ func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
for i, url := range urls {
ct := CheckContentType(client, url)
if strings.Contains(ct, "application/json") {
bodyBytes, _, _ := MakeRequest(client, "GET", url, timeout, nil, "brute")
bodyBytes, _, _ := MakeRequest(client, "GET", url, timeout, nil)
if bodyBytes != nil {
checkSpec := UnmarshalSpec(bodyBytes)
if (strings.HasPrefix(checkSpec.OpenAPI, "2") || strings.HasPrefix(checkSpec.OpenAPI, "3")) && checkSpec.Paths != nil {
@@ -113,7 +113,7 @@ func findDefinitionFile(urls []string, client http.Client) (bool, *openapi3.T) {
}
}
} else if strings.Contains(ct, "application/javascript") {
bodyBytes, bodyString, _ := MakeRequest(client, "GET", url, timeout, nil, "brute")
bodyBytes, bodyString, _ := MakeRequest(client, "GET", url, timeout, nil)
if bodyBytes != nil {
regexPattern := regexp.MustCompile(`(?s)let\s+(\w+)\s*=\s*({.*?});`)
matches := regexPattern.FindAllStringSubmatch(bodyString, -1)
+3 -3
View File
@@ -25,15 +25,15 @@ This list contains the raw endpoints (parameter values will not be appended or m
log.Infof("Gathering endpoints.\n\n")
if swaggerURL != "" {
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil, "endpoints")
paths = GenerateRequests(bodyBytes, client, "endpoints")
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil)
paths = GenerateRequests(bodyBytes, client)
} else {
specFile, err := os.Open(localFile)
if err != nil {
log.Fatal("Error opening file:", err)
}
bodyBytes, _ = io.ReadAll(specFile)
paths = GenerateRequests(bodyBytes, client, "endpoints")
paths = GenerateRequests(bodyBytes, client)
}
unique.Sort(unique.StringSlice{&paths})
for _, v := range paths {
+10
View File
@@ -14,6 +14,8 @@ func writeLog(sc int, target, method string, errorMsg string) {
logSkipped(sc, target, method)
} else if sc == 404 {
logNotFound(sc, target, method, errorMsg)
} else if sc == 1 {
logDangerous(target, method)
} else {
logManual(sc, target, method, errorMsg)
}
@@ -30,6 +32,14 @@ func logAccessible(status int, target, method string) {
}).Print("Endpoint accessible!")
}
func logDangerous(target, method string) {
log.WithFields(log.Fields{
"Status": "skipped",
"Target": target,
"Method": method,
}).Warn("Endpoint skipped due to dangerous keyword.")
}
func logManual(status int, target, method, errorMsg string) {
if errorMsg == "" {
errorMsg = "Manual testing may be required."
+9 -4
View File
@@ -12,7 +12,7 @@ import (
var prepareCmd = &cobra.Command{
Use: "prepare",
Short: "Prepares a set of commands for manual testing of each endpoint.",
Long: `The prepare command prepares a set of curl commands for manual testing of each endpoint.
Long: `The prepare command prepares a set of commands for manual testing of each endpoint.
This enables you to test specific API functions for common vulnerabilities or misconfigurations.`,
Run: func(cmd *cobra.Command, args []string) {
client := CheckAndConfigureProxy()
@@ -20,8 +20,8 @@ This enables you to test specific API functions for common vulnerabilities or mi
fmt.Printf("\n")
log.Infof("Gathering API details.\n\n")
if swaggerURL != "" {
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil, "prepare")
GenerateRequests(bodyBytes, client, "prepare")
bodyBytes, _, _ := MakeRequest(client, "GET", swaggerURL, timeout, nil)
GenerateRequests(bodyBytes, client)
} else {
specFile, err := os.Open(localFile)
if err != nil {
@@ -29,7 +29,12 @@ This enables you to test specific API functions for common vulnerabilities or mi
}
specBytes, _ := io.ReadAll(specFile)
GenerateRequests(specBytes, client, "prepare")
GenerateRequests(specBytes, client)
}
},
}
var prepareFor string
func init() {
prepareCmd.PersistentFlags().StringVarP(&prepareFor, "external-tool", "e", "curl", "The external tool to prepare commands for. Generates syntax for 'curl' by default.")
}
+11 -5
View File
@@ -7,6 +7,7 @@ import (
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"time"
@@ -51,25 +52,26 @@ var (
userChoice string
)
func MakeRequest(client http.Client, method, target string, timeout int64, reqData io.Reader, command string) ([]byte, string, int) {
func MakeRequest(client http.Client, method, target string, timeout int64, reqData io.Reader) ([]byte, string, int) {
if quiet {
avoidDangerousRequests = "y"
}
// Handling of dangerous keywords
u, _ := url.Parse(target)
endpoint := u.RawPath + "?" + u.RawQuery
for _, v := range dangerousStrings {
if command == "automate" && strings.Contains(endpoint, v) && !strings.Contains(strings.Join(safeWords, ","), v) {
if os.Args[1] == "automate" && strings.Contains(endpoint, v) && !strings.Contains(strings.Join(safeWords, ","), v) {
userChoice = ""
if avoidDangerousRequests == "y" {
return nil, "", 0
} else {
fmt.Printf("[!] Dangerous keyword '%s' detected in URL (%s). Do you still want to test this endpoint? (y/N)", v, target)
log.Warnf("Dangerous keyword '%s' detected in URL (%s). Do you still want to test this endpoint? (y/N)", v, target)
fmt.Scanln(&userChoice)
if strings.ToLower(userChoice) != "y" {
if !riskSurveyed {
avoidDangerousRequests = "y"
fmt.Printf("[!] Do you want to avoid all dangerous requests? (Y/n)")
log.Warnf("Do you want to avoid all dangerous requests? (Y/n)")
fmt.Scanln(&avoidDangerousRequests)
avoidDangerousRequests = strings.ToLower(avoidDangerousRequests)
riskSurveyed = true
@@ -106,6 +108,7 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
}
}
// User-Agent handling
if randomUserAgent {
if UserAgent != "Swagger Jacker (github.com/BishopFox/sj)" {
log.Fatalf("Cannot set a User Agent while supplying the 'random-user-agent' flag.")
@@ -137,9 +140,12 @@ func MakeRequest(client http.Client, method, target string, timeout int64, reqDa
log.Printf("Error: %s - skipping request.", err)
return nil, "", 0
} else if err != nil && err != context.Canceled && err != io.EOF {
log.Error("Error: response not received.\n", err)
if strings.Contains(fmt.Sprint(err), "tls") && !strings.Contains(fmt.Sprint(err), "user canceled") {
log.Fatal("Try supplying the --insecure flag.")
} else if strings.Contains(fmt.Sprint(err), "user canceled") {
return nil, "skipped", 1
} else {
log.Error("Error: response not received.\n", err)
}
return nil, "", 0
}
+4 -4
View File
@@ -32,7 +32,7 @@ Example usage:
Perform a quick check of endpoints which require authentication:
$ sj automate -u https://petstore.swagger.io/v2/swagger.json
Generate a list of curl commands to use for manual testing:
Generate a list of commands to use for manual testing:
$ sj prepare -u https://petstore.swagger.io/v2/swagger.json
Generate a list of raw API routes for use with custom scripts:
@@ -46,7 +46,7 @@ $ sj brute -u https://petstore.swagger.io`,
log.Error("Command not specified. See the --help flag for usage.")
}
},
Version: "1.3.4", // Also change in .goreleaser.yaml !
Version: "1.4.2",
}
func Execute() {
@@ -59,8 +59,8 @@ func init() {
rootCmd.AddCommand(prepareCmd)
rootCmd.AddCommand(bruteCmd)
rootCmd.PersistentFlags().StringVarP(&UserAgent, "agent", "a", "Swagger Jacker (github.com/BishopFox/sj)", "Set the User-Agent string.")
rootCmd.PersistentFlags().StringVarP(&basePath, "base-path", "b", "", "Set the API base path if not defined in the spec (i.e. /V2/)")
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "json", "Declare the format of the documentation (json/yaml/yml/js).")
rootCmd.PersistentFlags().StringVarP(&basePath, "base-path", "b", "", "Set the API base path if not defined in the definition file (i.e. /V2/).")
rootCmd.PersistentFlags().StringVarP(&format, "format", "f", "json", "Declare the format of the definition file (json/yaml/yml/js).")
rootCmd.PersistentFlags().StringArrayVarP(&Headers, "headers", "H", nil, "Add custom headers, separated by a colon (\"Name: Value\"). Multiple flags are accepted.")
rootCmd.PersistentFlags().BoolVarP(&insecure, "insecure", "i", false, "Ignores server certificate validation.")
rootCmd.PersistentFlags().StringVarP(&localFile, "local-file", "l", "", "Loads the documentation from a local file.")
+62 -20
View File
@@ -93,7 +93,7 @@ func ExtractSpecFromJS(bodyBytes []byte) []byte {
return bodyBytes
}
func GenerateRequests(bodyBytes []byte, client http.Client, command string) []string {
func GenerateRequests(bodyBytes []byte, client http.Client) []string {
var apiKey string
var apiKeyName string
var apiInQuery bool = false
@@ -107,16 +107,19 @@ func GenerateRequests(bodyBytes []byte, client http.Client, command string) []st
// BuildObjectsFromSchemaDefinitions(*newDoc) TODO
if command != "endpoints" {
if os.Args[1] != "endpoints" {
// Prints Title/Description values if they exist
PrintSpecInfo(*newDoc.Info)
apiInQuery, apiKey, apiKeyName = CheckSecDefs(*newDoc)
}
if command == "automate" && outfile != "" {
if os.Args[1] == "automate" && outfile != "" {
WriteJSONFile(`"results":[`, false)
}
if newDoc.Paths == nil {
log.Fatalf("Could not find any defined operations. Review the file manually.")
}
for path, pathItem := range newDoc.Paths {
operations := map[string]*openapi3.Operation{
"CONNECT": pathItem.Connect,
@@ -201,8 +204,8 @@ func GenerateRequests(bodyBytes []byte, client http.Client, command string) []st
u.Path = u.Path + newPath
u.RawQuery = query.Encode()
if command == "automate" {
_, _, sc := MakeRequest(client, method, u.String(), timeout, bytes.NewReader([]byte(bodyData)), command)
if os.Args[1] == "automate" {
_, _, sc := MakeRequest(client, method, u.String(), timeout, bytes.NewReader([]byte(bodyData)))
if sc == 200 {
accessibleEndpointFound = true
accessibleEndpoints = append(accessibleEndpoints, u.String())
@@ -225,31 +228,56 @@ func GenerateRequests(bodyBytes []byte, client http.Client, command string) []st
resultsJSON = append(resultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"}`, sc, u.String(), method, errorDescriptions[fmt.Sprint(sc)]))
}
} else {
resultsJSON = append(resultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"}`, sc, u.String(), method, errorDescriptions[fmt.Sprint(sc)]))
if sc == 1 {
resultsJSON = append(resultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"Skipped due to dangerous keyword in request"}`, sc, u.String(), method))
} else {
resultsJSON = append(resultsJSON, fmt.Sprintf(`{"status_code":"%d","url":"%s","method":"%s","details":"%s"}`, sc, u.String(), method, errorDescriptions[fmt.Sprint(sc)]))
}
}
} else if !getAccessibleEndpoints && strings.ToLower(outputFormat) == "console" {
writeLog(sc, u.String(), method, errorDescriptions[fmt.Sprint(sc)])
}
}
} else if command == "prepare" {
if bodyData == nil {
if len(Headers) == 0 {
fmt.Printf("curl -sk -X %s '%s'\n", method, u.String())
} else if os.Args[1] == "prepare" {
if strings.ToLower(prepareFor) == "curl" {
if bodyData == nil {
if len(Headers) == 0 {
fmt.Printf("curl -sk -X %s '%s'\n", method, u.String())
} else {
fmt.Printf("curl -sk -X %s '%s' -H '%s'\n", method, u.String(), strings.Join(Headers, "' -H '"))
}
} else {
fmt.Printf("curl -sk -X %s '%s' -H '%s'\n", method, u.String(), strings.Join(Headers, "' -H '"))
if len(Headers) == 0 {
fmt.Printf("curl -sk -X %s '%s' -d '%s'\n", method, u.String(), bodyData)
} else {
fmt.Printf("curl -sk -X %s '%s' -d '%s' -H '%s'\n", method, u.String(), bodyData, strings.Join(Headers, "' -H '"))
}
}
} else if strings.ToLower(prepareFor) == "sqlmap" {
if bodyData == nil {
if len(Headers) == 0 {
fmt.Printf("sqlmap -u %s\n", u.String())
} else {
fmt.Printf("sqlmap -u %s -H '%s'\n", u.String(), strings.Join(Headers, "' -H '"))
}
} else {
if len(Headers) == 0 {
fmt.Printf("sqlmap -u %s --data='%s'\n", u.String(), bodyData)
} else {
fmt.Printf("sqlmap -u %s --data='%s' -H '%s'\n", u.String(), bodyData, strings.Join(Headers, "' -H '"))
}
}
} else if strings.ToLower(prepareFor) == "ffuf" {
// TODO
} else {
if len(Headers) == 0 {
fmt.Printf("curl -sk -X %s '%s' -d '%s'\n", method, u.String(), bodyData)
} else {
fmt.Printf("curl -sk -X %s '%s' -d '%s' -H '%s'\n", method, u.String(), bodyData, strings.Join(Headers, "' -H '"))
}
log.Fatal("External tool not supported. Only 'curl' and 'sqlmap' are supported options for the '-e' flag at this time.")
}
} else if command == "endpoints" {
} else if os.Args[1] == "endpoints" {
for k := range newDoc.Paths {
if !pathMap[k] {
paths = append(paths, k)
paths = append(paths, basePathResult+k)
pathMap[k] = true
}
}
@@ -259,7 +287,7 @@ func GenerateRequests(bodyBytes []byte, client http.Client, command string) []st
}
}
if command == "automate" {
if os.Args[1] == "automate" {
if outfile == "" && getAccessibleEndpoints && strings.ToLower(outputFormat) == "console" {
var isDuplicateEndpoint bool
var printedEndpoints []string
@@ -312,6 +340,10 @@ func GetBasePath(servers openapi3.Servers, host string) (bp string) {
basePath = "/"
} else {
basePath = servers[0].URL
if strings.Contains(host, ":") {
hostPortStart := strings.Index(host, ":")
host = host[0:hostPortStart]
}
if strings.Contains(basePath, host) || strings.Contains(basePath, "http") {
basePath = strings.ReplaceAll(basePath, host, "")
basePath = strings.ReplaceAll(basePath, "http://", "")
@@ -338,6 +370,10 @@ func PrintSpecInfo(i openapi3.Info) {
if i.Description != "" {
fmt.Printf("Description: %s\n\n", i.Description)
}
if i.Title == "" && i.Description == "" {
log.Warnf("Detected possible error in parsing the definition file. Title and description values are empty.\n\n")
}
}
}
@@ -384,12 +420,18 @@ func UnmarshalSpec(bodyBytes []byte) (newDoc *openapi3.T) {
if strings.HasPrefix(doc3.OpenAPI, "3") {
newDoc := &doc3
return newDoc
} else {
} else if strings.HasPrefix(doc.Swagger, "2") {
newDoc, err := openapi2conv.ToV3(&doc)
if err != nil {
fmt.Printf("Error converting v2 document to v3: %s\n", err)
}
return newDoc
} else if os.Args[1] == "brute" {
var noDoc openapi3.T
return &noDoc
} else {
log.Fatal("Error parsing definition file.\n")
return nil
}
}