Openapi-generator: [BUG] PHPSlim4 - Want only 1 authentication method out of multiple

Created on 17 Nov 2019  路  9Comments  路  Source: OpenAPITools/openapi-generator

Bug Report Checklist

  • [x] Have you provided a full/minimal spec to reproduce the issue?
  • [x] Have you validated the input using an OpenAPI validator (example)?
  • [x] What's the version of OpenAPI Generator used? 4.2.1
  • [x] Have you search for related issues/PRs?
  • [x] What's the actual output vs expected output?
    Actual: adds all security schemes in middlewares that all get executed.
    Expected: only one security scheme should match.
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.

Authentication Bug PHP

All 9 comments

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

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

Was this page helpful?
0 / 5 - 0 ratings