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

OperationId behaviour question #1949

Open
starcore2022 opened this issue Jul 19, 2024 · 0 comments
Open

OperationId behaviour question #1949

starcore2022 opened this issue Jul 19, 2024 · 0 comments

Comments

@starcore2022
Copy link

starcore2022 commented Jul 19, 2024

Description

I have been trying out Connexion recently. I have an API spec that is like a 3GPP API spec in yaml format. I noticed that all the given examples follow the "PythonAppName.runThisFunction" operationId format. Also found examples where only the name of the function is defined.
I would like to now, if it is mandatory to follow the mentioned format or the function name is enough?
I wrote a simple code where it should give back a fix answer.

Expected behaviour

According to the swagger OpenAPI guide (https://swagger.io/docs/specification/paths-and-operations/#operations), it should be enough to specify the function name only. The middleware should recognise that operationId function is defined in my python code. In it I start the AsyncApp with the run() function.

Actual behaviour

With: "operationId: Login"
Failed to add operation for POST /ss-nra/v1/login
and if I try to reach it regardless: Internal server error 500, empty module name

With: "operationId: app.Login"
No error, but a securitySchemes warning of a missing x-term (x-tokenInfoFunc missing), but the server is reachable and there is no empty module name.
Also recieves the expected response.

Steps to reproduce

Create "mockupserver.py", code:

import socket
import os
from connexion import AsyncApp, ConnexionMiddleware, request, App
from pathlib import Path

import connexion
from threading import Timer
import json

TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImZvbyIsInBhc3N3b3JkIjoiYmFyIiwiaWF0IjoxNjY4MDg0NDI1fQ.lT4ABOQSHyJdIiF9rso06qcwrBkIxRFyolIgdBAI4l0"
PASSWD = {"admin": "secret", "foo": "bar"}

#Simulating an authentication process
#Should more secure than this
#@app.route("/login")
def Login(body):
    # data = {
    #     'username': str,
    #     'password': str,
    #     'accessToken': str
    # }
    if body is not json:
        body = json.loads(body)

    data = {
            'username': body['username'],
            'password': body['password'],
            'accessToken': ""
    }

    if PASSWD.get(body.username) == body.password:
        print("Right password and username")
        headers = {"Content-Type": "application/json"}
        data['accessToken'] = TOKEN
        return data, 200, headers
    else:
        print("Wrong username ('foo') or password ('bar')")
        print(": "+body['username']+" "+body['password'])
        headers = {"Content-Type": "application/json"}
        data['accessToken'] = TOKEN
        return data, 200, headers

def Login_Get(body):

    data = {
            'username': 'foo',
            'password': 'bar',
            'accessToken': ""
    }

    print("Right password and username")
    data['accessToken'] = TOKEN
    headers = {"Content-Type": "application/json"}
    return data, 200, headers

##Create the server
app = AsyncApp(__name__, specification_dir="./")

##Add the required APIs
app.add_api("login.yaml")

try:
    if __name__ == '__main__':
        #Start server
        app.run(f"{Path(__file__).stem}:app", port=7777)
except KeyboardInterrupt:
    #Ater KeyboardInterrupt
    print("CTRL-C: Exiting")

print("CTRL-C: Exiting")

Create "login.yaml" API spec:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: custom_unprotected_login_action
  description: A custom login to get access token

security:
  - {}
  - oAuth2ClientCredentials: []

servers:
  - url: '{apiRoot}/ss-nra/v1'
    variables:
      apiRoot:
        default: https://local_example.com

paths:
  /login:
    post:
     tags:
        - Access Token (Collection)
     summary: On successful login, give back access token
     operationId: mockupserver.Login
     requestBody:
        required: true
        content:
          application/json:
              schema:
                $ref: '#/components/schemas/login_info'
     responses:
        '200':
          description: User login success
          content:
           application/json:
               schema:
                 $ref: '#/components/schemas/login_info'
  /login:
    get:
     tags:
        - Access Token 2 (Collection)
     summary: On successful login, give back access token
     operationId: mockupserver.Login_Get
     requestBody:
        content:
          application/json:
              schema:
                $ref: '#/components/schemas/login_info'
     responses:
        '200':
          description: User login success
          content:
           application/json:
               schema:
                 $ref: '#/components/schemas/login_info'


components:
  securitySchemes:
    oAuth2ClientCredentials:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: '{tokenUrl}'
          scopes: {}            

  schemas:
    login_info:
      description: Represents a unicast subscription.
      type: object
      properties:
        username:
          type: string
        password:
          type: string
        accessToken:
          type: string
      required:
        - username
        - password

Tester code "loginer.py":

from urllib import request
from urllib import error
import json

URL = "https://local_example.com"
PATH = "/ss-nra/v1/login"
LINK_URL = "http://127.0.0.1:7777"
METHOD = ['GET','POST']
HEADERS = {'Content-Type':'application/json'}

login_body = {
    'username':'foo',
    'password':'bar'
}

login_body = json.dumps(login_body)
print(login_body)
login_body = login_body.encode()
print(login_body)

try:
    req = request.Request(LINK_URL+PATH,method=METHOD[0])
    req.add_header('Content-Type','application/json')
    r = request.urlopen(req,data=login_body)

    response_data = r.read().decode('utf-8')
    print(response_data)
except error.URLError as e:
    if hasattr(e, 'reason'):
        print('We failed to reach a server.')
        print('Reason: ', e.reason)
    elif hasattr(e, 'code'):
        print('The server couldn\'t fulfill the request.')
        print('Error code: ', e.code)
    else:
        # everything is fine
        # not used
        ret_code = 200

Run: python3 mockupserver.py
Run: python3 loginer.py

Then repeat with the login.yaml modified to this:

openapi: "3.0.0"
info:
  version: 1.0.0
  title: custom_unprotected_login_action
  description: A custom login to get access token

security:
  - {}
  - oAuth2ClientCredentials: []

servers:
  - url: '{apiRoot}/ss-nra/v1'
    variables:
      apiRoot:
        default: https://local_example.com

paths:
  /login:
    post:
     tags:
        - Access Token (Collection)
     summary: On successful login, give back access token
     operationId: Login
     requestBody:
        required: true
        content:
          application/json:
              schema:
                $ref: '#/components/schemas/login_info'
     responses:
        '200':
          description: User login success
          content:
           application/json:
               schema:
                 $ref: '#/components/schemas/login_info'
  /login:
    get:
     tags:
        - Access Token 2 (Collection)
     summary: On successful login, give back access token
     operationId: Login_Get
     requestBody:
        content:
          application/json:
              schema:
                $ref: '#/components/schemas/login_info'
     responses:
        '200':
          description: User login success
          content:
           application/json:
               schema:
                 $ref: '#/components/schemas/login_info'


components:
  securitySchemes:
    oAuth2ClientCredentials:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: '{tokenUrl}'
          scopes: {}            

  schemas:
    login_info:
      description: Represents a unicast subscription.
      type: object
      properties:
        username:
          type: string
        password:
          type: string
        accessToken:
          type: string
      required:
        - username
        - password


Additional info:

Output of the commands:

  • python3 --version Python 3.10.12
  • pip show connexion | grep "^Version\:" Version: 3.1.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant