OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.64k stars 6.53k forks source link

[BUG] PHPSlim4 - Want only 1 authentication method out of multiple #4513

Open Sroose opened 4 years ago

Sroose commented 4 years ago

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:

$this->addRoute(..
                $middlewares
            )->setName($operation['operationId']);
openapi-generator version

4.2.1

OpenAPI declaration file content or url
        get:
            ...
            security:
                -
                    AdminToken: []
                -
                    UserJWT: []
                -
                    ExternalToken: []
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.

ybelenko commented 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.

ybelenko commented 4 years ago

@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.

wing328 commented 4 years ago

I'll take a look and reply over the weekend.

richardwhiuk commented 4 years ago

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.

ybelenko commented 4 years ago

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.

ybelenko commented 4 years ago

@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.

ybelenko commented 4 years ago

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
wing328 commented 4 years ago

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.

ybelenko commented 4 years ago

@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.