Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
5 tasks done
Sroose opened this issue Nov 17, 2019 · 9 comments
Open
5 tasks done

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

Sroose opened this issue Nov 17, 2019 · 9 comments

Comments

@Sroose
Copy link

Sroose commented Nov 17, 2019

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • What's the version of OpenAPI Generator used? 4.2.1
  • Have you search for related issues/PRs?
  • 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 #3844 for Python. Also #797 seems relavant.

@ybelenko
Copy link
Contributor

@Sroose Thanks for feedback.

I'll add another layer of middleware that checks if request has been authorized by any auth schema.

@ybelenko
Copy link
Contributor

ybelenko commented Feb 4, 2020

@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
Copy link
Member

wing328 commented Feb 21, 2020

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

@richardwhiuk
Copy link
Contributor

richardwhiuk commented Feb 22, 2020

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
Copy link
Contributor

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
Copy link
Contributor

@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
Copy link
Contributor

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
Copy link
Member

wing328 commented Feb 24, 2020

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
Copy link
Contributor

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants