Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions openapi2kong/openapi2kong.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,28 +162,30 @@ func getOIDCdefaults(
scheme *v3.SecurityScheme // the security-scheme object
)
{
if len(requirements) == 0 || ignoreSecurityErrors {
// no security requirements or nothing is defined
// so return inherited (can be nil)
if len(requirements) == 0 {
return inherited, nil
}

if len(requirements) > 1 && !ignoreSecurityErrors {
if len(requirements) > 1 {
if ignoreSecurityErrors {
return inherited, nil
}
return nil, fmt.Errorf("only a single security-requirement is supported")
}

requirement := requirements[0].Requirements
if requirement.Len() == 0 || ignoreSecurityErrors {
return inherited, nil // there is nothing defined, so return inherited (can be nil)

if requirement.Len() == 0 {
return inherited, nil
}

if requirement.Len() > 1 && !ignoreSecurityErrors {
// multiple schemes are a logical AND, which is not supported
if requirement.Len() > 1 {
if ignoreSecurityErrors {
return inherited, nil
}
return nil, fmt.Errorf("within a security-requirement only a single security-scheme is supported")
}

// requirement has only 1 entry
// So, we won't iterate
reqPair := requirement.First()
schemeName = reqPair.Key()
scopes = reqPair.Value()
Expand All @@ -192,20 +194,18 @@ func getOIDCdefaults(
scheme, _ = schemes.Get(schemeName)

if scheme == nil {
if !ignoreSecurityErrors {
return nil, fmt.Errorf("no security-schemes with name '%s' found in components", schemeName)
if ignoreSecurityErrors {
return inherited, nil
}
return inherited, nil
return nil, fmt.Errorf("no security-schemes with name '%s' found in components", schemeName)
}

// Check if scheme type is openIdConnect (case-insensitive, accepting camelCase, kebab-case and snake_case)
normalizedType := strings.ToLower(strings.ReplaceAll(strings.ReplaceAll(scheme.Type, "_", ""), "-", ""))
if normalizedType != "openidconnect" {
// non-OIDC security directives are not supported
if !ignoreSecurityErrors {
return nil, fmt.Errorf("only security-schemes of type 'openIdConnect' are supported")
if ignoreSecurityErrors {
return inherited, nil
Comment on lines +205 to +206

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Do not inherit OIDC for ignored operation overrides

When a document-level OIDC scheme is valid but an operation overrides security with an unsupported scheme such as apiKey, Convert calls getOIDCdefaults(operation.Security, doc, docOIDCdefaults, true). This branch returns the inherited document OIDC config, so the later string comparison treats the operation as unchanged and leaves the service-level openid-connect plugin applying to that route, even though OpenAPI operation security overrides the document-level requirement and --ignore-security-errors should skip the unsupported operation security rather than reapply the parent.

Useful? React with 👍 / 👎.

}
return inherited, nil
return nil, fmt.Errorf("only security-schemes of type 'openIdConnect' are supported")
}
}

Expand Down
110 changes: 110 additions & 0 deletions openapi2kong/openapi2kong_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,116 @@ func Test_Openapi2kong_IgnoreCircularRefs(t *testing.T) {
}
}

func Test_Openapi2kong_IgnoreSecurityErrors(t *testing.T) {
t.Run("still generates valid openid-connect plugin", func(t *testing.T) {
testDataString := `
openapi: 3.0.0
info:
title: OIDC Test API
version: "1.0"
servers:
- url: https://api.example.com
security:
- OpenIDConnect:
- profile
- email
paths:
/widgets:
get:
operationId: listWidgets
responses:
"200":
description: OK
components:
securitySchemes:
OpenIDConnect:
type: openIdConnect
openIdConnectUrl: https://issuer.example.com/.well-known/openid-configuration
`

dataOut, err := Convert([]byte(testDataString), O2kOptions{
OIDC: true,
IgnoreSecurityErrors: true,
SkipID: true,
})

assert.NoError(t, err)

plugins, ok := dataOut["plugins"].([]*map[string]interface{})
assert.True(t, ok, "expected top-level plugins array")

var oidcPlugin *map[string]interface{}
for _, plugin := range plugins {
if (*plugin)["name"] == "openid-connect" {
oidcPlugin = plugin
break
}
}

if assert.NotNil(t, oidcPlugin, "expected openid-connect plugin to be generated") {
config, ok := (*oidcPlugin)["config"].(map[string]interface{})
assert.True(t, ok, "expected openid-connect plugin config")

assert.Equal(t,
"https://issuer.example.com/.well-known/openid-configuration",
config["issuer"],
)

assert.Equal(t,
[]string{"email", "profile"},
config["scopes_required"],
)
}
})

t.Run("suppresses unsupported non-openid-connect security scheme", func(t *testing.T) {
testDataString := `
openapi: 3.0.0
info:
title: API Key Test API
version: "1.0"
servers:
- url: https://api.example.com
security:
- ApiKeyAuth: []
paths:
/widgets:
get:
operationId: listWidgets
responses:
"200":
description: OK
components:
securitySchemes:
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
`

_, err := Convert([]byte(testDataString), O2kOptions{
OIDC: true,
})
assert.Error(t, err)
assert.Contains(t, err.Error(), "only security-schemes of type 'openIdConnect' are supported")

dataOut, err := Convert([]byte(testDataString), O2kOptions{
OIDC: true,
IgnoreSecurityErrors: true,
SkipID: true,
})

assert.NoError(t, err)

plugins, ok := dataOut["plugins"].([]*map[string]interface{})
if ok {
for _, plugin := range plugins {
assert.NotEqual(t, "openid-connect", (*plugin)["name"])
}
}
})
}

func Test_Openapi2kong_pathParamLength(t *testing.T) {
testDataString := `
openapi: 3.0.3
Expand Down
Loading