Open Sroose opened 4 years ago
@Sroose Thanks for feedback.
I'll add another layer of middleware that checks if request has been authorized by any auth schema.
@wing328 or @jimschubert help me figure out specification. Spec says about Security Requirement Object:
Security Requirement Objects that contain multiple schemes require that all schemes MUST be satisfied for a request to be authorized. This enables support for scenarios where multiple query parameters or HTTP headers are required to convey security information.
When a list of Security Requirement Objects is defined on the OpenAPI Object or Operation Object, only one of the Security Requirement Objects in the list needs to be satisfied to authorize the request.
Can you provide two spec examples when ALL security schemes MUST be satisfied and when ANY security schema from a list MUST be satisfied. It's kinda important, I can add massive security hole because of misunderstanding here.
I'll take a look and reply over the weekend.
Should be confirmed, but my understanding of the spec is:
openapi: 3.0.1
paths:
/security-requirement-all:
post:
security:
- api_key: []
oauth: ["scope"]
/security-requirement-one-of:
post:
security:
- api_key: []
- oauth: ["scope"]
components:
securitySchemes:
api_key:
type: apiKey
name: X-Api-Key
in: header
oauth:
type: oauth2
flow:
implicit:
authorizationUrl: https://example.com/api/oauth/dialog
scopes:
scope: sample scope
The first specifies a Security Requirement object with two requirements.
The second gives two options for the Security Requirement object, one of which must be satisfied.
Thanks @richardwhiuk . I've checked provided spec briefly and it passes validation. The only misspell is flow
instead of flows
in oauth
object. I'll take a deeper look today.
@jimschubert and @wing328 please confirm, that we understand provided spec 100% correctly. Confirm that ALL security schemas MUST be satisfied in /security-requirement-all
endpoint and that ONE of security schema MUST be satisfied in /security-requirement-one-of
endpoint.
I just understood that it can be even more complex:
paths:
/security-requirement-all:
post:
security:
## (apiKey AND oauth) OR basic
- api_key: []
oauth: ["scope"]
- http_basic: []
responses:
200:
description: Success
/security-requirement-one-of:
post:
security:
## apiKey OR oauth OR (apiKey AND basic)
- api_key: []
- oauth: ["scope"]
- api_key: []
http_basic: []
responses:
200:
description: Success
components:
securitySchemes:
http_basic:
type: http
scheme: basic
api_key:
type: apiKey
name: X-Api-Key
in: header
oauth:
type: oauth2
flows:
implicit:
authorizationUrl: https://example.com/api/oauth/dialog
scopes:
scope: sample scope
I think the example provided by @richardwhiuk is correct to explain AND, OR in security definitions for endpoints.
Your example (even more complex) is also correct.
@wing328 I've spend an hour yesterday and realized that both endpoints contains flat array in codegen variables. Codegen variables below:
{
"operations": {
"classname": "AbstractDefaultApi",
"operation": [
{
"responseHeaders": [],
"hasAuthMethods": true,
"hasConsumes": false,
"hasProduces": false,
"hasParams": false,
"hasOptionalParams": false,
"hasRequiredParams": false,
"returnTypeIsPrimitive": false,
"returnSimpleType": false,
"subresourceOperation": false,
"isMapContainer": false,
"isListContainer": false,
"isMultipart": false,
"hasMore": true,
"isResponseBinary": false,
"isResponseFile": false,
"hasReference": false,
"isRestfulIndex": false,
"isRestfulShow": false,
"isRestfulCreate": false,
"isRestfulUpdate": false,
"isRestfulDestroy": false,
"isRestful": false,
"isDeprecated": false,
"isCallbackRequest": false,
"path": "/security-requirement-all",
"operationId": "securityRequirementAllPost",
"httpMethod": "POST",
"baseName": "Default",
"servers": [],
"allParams": [],
"bodyParams": [],
"pathParams": [],
"queryParams": [],
"headerParams": [],
"formParams": [],
"cookieParams": [],
"requiredParams": [],
"optionalParams": [],
"authMethods": [
{
"name": "api_key",
"type": "apiKey",
"hasMore": true,
"isBasic": false,
"isOAuth": false,
"isApiKey": true,
"isBasicBasic": false,
"isBasicBearer": false,
"isHttpSignature": false,
"vendorExtensions": {},
"keyParamName": "X-Api-Key",
"isKeyInQuery": false,
"isKeyInHeader": true,
"isKeyInCookie": false,
"isCode": false,
"isPassword": false,
"isApplication": false,
"isImplicit": false
},
{
"name": "http_basic",
"type": "http",
"scheme": "basic",
"hasMore": true,
"isBasic": true,
"isOAuth": false,
"isApiKey": false,
"isBasicBasic": true,
"isBasicBearer": false,
"isHttpSignature": false,
"vendorExtensions": {},
"isKeyInQuery": false,
"isKeyInHeader": false,
"isKeyInCookie": false,
"isCode": false,
"isPassword": false,
"isApplication": false,
"isImplicit": false
},
{
"name": "oauth",
"type": "oauth2",
"hasMore": false,
"isBasic": false,
"isOAuth": true,
"isApiKey": false,
"isBasicBasic": false,
"isBasicBearer": false,
"isHttpSignature": false,
"vendorExtensions": {},
"isKeyInQuery": false,
"isKeyInHeader": false,
"isKeyInCookie": false,
"flow": "implicit",
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": [
{
"scope": "scope",
"description": "sample scope"
}
],
"isCode": false,
"isPassword": false,
"isApplication": false,
"isImplicit": true
}
],
"tags": [
{
"name": "default"
}
],
"callbacks": [],
"imports": [],
"vendorExtensions": {},
"nickname": "securityRequirementAllPost",
"operationIdLowerCase": "securityrequirementallpost",
"operationIdCamelCase": "SecurityRequirementAllPost",
"operationIdSnakeCase": "security_requirement_all_post",
"restfulShow": false,
"restfulIndex": false,
"restfulCreate": false,
"restfulUpdate": false,
"restfulDestroy": false,
"restful": false,
"hasFormParams": false,
"hasExamples": false,
"hasBodyParam": false,
"hasPathParams": false,
"hasQueryParams": false,
"hasHeaderParams": false,
"hasCookieParams": false,
"hasResponseHeaders": false,
"bodyAllowed": true
},
{
"responseHeaders": [],
"hasAuthMethods": true,
"hasConsumes": false,
"hasProduces": false,
"hasParams": false,
"hasOptionalParams": false,
"hasRequiredParams": false,
"returnTypeIsPrimitive": false,
"returnSimpleType": false,
"subresourceOperation": false,
"isMapContainer": false,
"isListContainer": false,
"isMultipart": false,
"hasMore": false,
"isResponseBinary": false,
"isResponseFile": false,
"hasReference": false,
"isRestfulIndex": false,
"isRestfulShow": false,
"isRestfulCreate": false,
"isRestfulUpdate": false,
"isRestfulDestroy": false,
"isRestful": false,
"isDeprecated": false,
"isCallbackRequest": false,
"path": "/security-requirement-one-of",
"operationId": "securityRequirementOneOfPost",
"httpMethod": "POST",
"baseName": "Default",
"servers": [],
"allParams": [],
"bodyParams": [],
"pathParams": [],
"queryParams": [],
"headerParams": [],
"formParams": [],
"cookieParams": [],
"requiredParams": [],
"optionalParams": [],
"authMethods": [
{
"name": "api_key",
"type": "apiKey",
"hasMore": true,
"isBasic": false,
"isOAuth": false,
"isApiKey": true,
"isBasicBasic": false,
"isBasicBearer": false,
"isHttpSignature": false,
"vendorExtensions": {},
"keyParamName": "X-Api-Key",
"isKeyInQuery": false,
"isKeyInHeader": true,
"isKeyInCookie": false,
"isCode": false,
"isPassword": false,
"isApplication": false,
"isImplicit": false
},
{
"name": "http_basic",
"type": "http",
"scheme": "basic",
"hasMore": true,
"isBasic": true,
"isOAuth": false,
"isApiKey": false,
"isBasicBasic": true,
"isBasicBearer": false,
"isHttpSignature": false,
"vendorExtensions": {},
"isKeyInQuery": false,
"isKeyInHeader": false,
"isKeyInCookie": false,
"isCode": false,
"isPassword": false,
"isApplication": false,
"isImplicit": false
},
{
"name": "oauth",
"type": "oauth2",
"hasMore": false,
"isBasic": false,
"isOAuth": true,
"isApiKey": false,
"isBasicBasic": false,
"isBasicBearer": false,
"isHttpSignature": false,
"vendorExtensions": {},
"isKeyInQuery": false,
"isKeyInHeader": false,
"isKeyInCookie": false,
"flow": "implicit",
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": [
{
"scope": "scope",
"description": "sample scope"
}
],
"isCode": false,
"isPassword": false,
"isApplication": false,
"isImplicit": true
}
],
"tags": [
{
"name": "default"
}
],
"callbacks": [],
"imports": [],
"vendorExtensions": {},
"nickname": "securityRequirementOneOfPost",
"operationIdLowerCase": "securityrequirementoneofpost",
"operationIdCamelCase": "SecurityRequirementOneOfPost",
"operationIdSnakeCase": "security_requirement_one_of_post",
"restfulShow": false,
"restfulIndex": false,
"restfulCreate": false,
"restfulUpdate": false,
"restfulDestroy": false,
"restful": false,
"hasFormParams": false,
"hasExamples": false,
"hasBodyParam": false,
"hasPathParams": false,
"hasQueryParams": false,
"hasHeaderParams": false,
"hasCookieParams": false,
"hasResponseHeaders": false,
"bodyAllowed": true
}
],
"pathPrefix": "default",
"userClassname": "DefaultApi"
}
}
It seems to me that we need securityJsonSchema
codegen variable which looks like jsonSchema
in responses
nodes. It will make possible to keep OR|AND logic.
Bug Report Checklist
Description
The generator does not take into account the fact that multiple security schemes are defined as 'OR'. In other words, the generated PHP Slim code will always try to perform all authentication methods simultaneously.
They are added in the middewares like this:
openapi-generator version
4.2.1
OpenAPI declaration file content or url
Steps to reproduce
Create an OpenApi operation with multiple possible security schemes.
Related issues/PRs
Looks similar to https://github.com/OpenAPITools/openapi-generator/issues/3844 for Python. Also https://github.com/OpenAPITools/openapi-generator/issues/797 seems relavant.