7

Point out the right document, tutorial, example, or provide one, showing how to add a specific authentication token to a specific header in the Swagger generated API client in Python?

Here's what I've tried:
My API call works just fine with the right curl command:

curl -v -H 'X-CAG-Authorization: AG_CONSUMER_TOKEN access-key=31337-70k3n' \
     'https://api.company.net/api/v1/user/detail?user=1'

*   Trying 10.10.1.10...
* Connected to api.company.net (10.10.1.10) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
* Server certificate: *.company.net
* Server certificate: COMODO RSA Organization Validation Secure Server CA
* Server certificate: COMODO RSA Certification Authority
> GET /api/v1/user/detail?user=1 HTTP/1.1
> Host: api.company.net
> User-Agent: curl/7.49.1
> Accept: */*
> X-CAG-Authorization: AG_CONSUMER_TOKEN access-key=31337-70k3n
> 
< HTTP/1.1 200 OK
< Server: openresty
< Date: Thu, 22 Dec 2016 19:46:05 GMT
< Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
< Connection: close
< Vary: Accept-Encoding
< Vary: Accept-Encoding
< X-UA-Compatible: IE=edge
< 
{"successful":true,"message":"SUCCESS","body":{"…

However, when I try the same basic request in my Python (2.7.12) client I get an authorization failure, despite confirming that the token makes it into the header about to be used. More details on the right way to use the client OR on how to get more details of the exact request and response would be appreciated.

/Users/me/VEnvs/sku-grade/bin/python /Users/me/prj/code/python_client/api_example.py
HEADERS:
{'X-CAG-Authorization': 'AG_CONSUMER_TOKEN access-key=31337-70k3n', 'User-Agent': 'Swagger-Codegen/1.0.0/python'}
Exception when calling SupplierApi->get_api_v1_user_details: (401)
Reason: Unauthorized
HTTP response headers: HTTPHeaderDict({'Date': 'Thu, 22 Dec 2016 21:09:30 GMT', 'Content-Length': '636', 'Content-Type': 'application/json; charset=UTF-8', 'Connection': 'keep-alive', 'Server': 'nginx'})
HTTP response body: {
  "code" : "PRECONDITION_FAILED",
  "type" : "UnauthorizedApiDeniedException",
  "message" : "Target API(/api/v1/user/details) is not available, you have to get a grant in advance.",
  "messages" : {…

Here's a swagger api spec: swagger.yaml

---
swagger: "2.0"
info:
  description: "API"
  version: "TEMPORARY"
  title: "User Details"
  termsOfService: "http://wiki.company.net/tos"
  contact:
    name: "…"
  license:
    name: "…"
host: "api.company.net"
basePath: "/api/v1"
tags:
- name: "supplier"
  description: "Supplier"
schemes:
- "https"
produces:
- "application/json"
paths:
  /user/details:
    get:
      tags:
      - "supplier"
      summary: "userDetails"
      operationId: "getApiV1UserDetails"
      consumes:
      - "application/json"
      produces:
      - "application/json;charset=utf-8"
      parameters:
      - name: "user"
        in: "query"
        description: "user id"
        required: true
        type: "integer"
        format: "Long"
      responses:
        200:
          description: "OK"
          schema:
            $ref: "#/definitions/SupplierResponseOfUserDetailsDto"
        401:
          description: "Unauthorized"
        403:
          description: "Forbidden"
        404:
          description: "Not Found"
definitions:
  SupplierResponseOfUserDetailsDto:
    type: "object"
    properties:
      body:
        $ref: "#/definitions/UserDetailsDto"
      message:
        type: "string"
      successful:
        type: "boolean"
  UserDetailsDto:
    type: "object"
    properties:
      name:
        type: "string"

The swagger-codegen was run from http://editor.swagger.io/ and I followed the api example trying to add in the extra header: api_example.py

from __future__ import print_function
import time
import swagger_client
from swagger_client import ApiClient
from swagger_client import Configuration
from swagger_client.rest import ApiException
from pprint import pprint

# Setup the authentication token header
conf = Configuration()
conf.api_key_prefix = {"teamname": "AG_CONSUMER_TOKEN"}
conf.api_key = {
    "teamname": "access-key=31337-70k3n"
}
conf.api_client = ApiClient(None, "X-CAG-Authorization",
                            conf.get_api_key_with_prefix("teamname"))

# create an instance of the API class
api_instance = swagger_client.SupplierApi()
user = 1
try:
    api_response = api_instance.get_api_v1_user_details(user)
    pprint(api_response)
except ApiException as e:
    print("Exception when calling "
          "SupplierApi->get_api_v1_user_details: %s\n" % e)

By putting a print(self.api_client.default_headers) into the supplier_api.py I could see that the header did appear to be set.

{'X-CAG-Authorization': 'AG_CONSUMER_TOKEN access-key=31337-70k3n', 'User-Agent': 'Swagger-Codegen/1.0.0/python'}

So again what should I change in my example to get it to pass on the header and get authorized exactly the way a simple curl call does?

Update I've also tried defining it:

      security:
      - api_key: []
securityDefinitions:
  api_key:
    type: "apiKey"
    name: "X-CAG-Authorization"
    in: "header"

and then only setting up the key with:

swagger_client.configuration.api_key['X-CAG-Authorization'] = \
    'access-key=31337-70k3n'
swagger_client.configuration.api_key_prefix['X-CAG-Authorization'] = \
    'AG_CONSUMER_TOKEN'

But that didn't change much other than the header disappears from the default headers I was printing.

dlamblin
  • 43,965
  • 20
  • 101
  • 140
  • I also tried referencing the configuration via `swagger_client.configuration.…` for `api_key_prefix` `api_key` and `api_client` which worked identically to the above; later I changed my spec to include a `securityDefinitions` and mention of the `security` then removed these configuration settings and instead set only the api_key by name. That didn't affect the header nor change behavior. – dlamblin Dec 22 '16 at 22:36
  • I've also opened this as an issue with the swagger codegen project: https://github.com/swagger-api/swagger-codegen/issues/4456 – dlamblin Dec 29 '16 at 17:18

2 Answers2

3

I've tried your code example and it looks like your headers are actually passed to server.

You can confirm this by adding print headers to swagger_client/rest.py file, just before this:

r = self.pool_manager.request(method, url,
                              fields=query_params,
                              preload_content=_preload_content,
                              timeout=timeout,
                              headers=headers)

Are you sure there is no problem on server side? Maybe some headers breaking the authentication?

Does the following curl command also work?

curl -v \
 -H 'X-CAG-Authorization: AG_CONSUMER_TOKEN access-key=31337-70k3n' \
 -H 'Content-Type: application/json' \
 -H 'Accept: application/json;charset=utf-8' \
 -H 'User-Agent: Swagger-Codegen/1.0.0/python' \
 'https://api.company.net/api/v1/user/detail?user=1'

Because this should give you the exact same answer that returned to swagger, which is 401 error. If it does, you can debug from there on server side. If it doesn't, I have no idea.

previous_developer
  • 10,579
  • 6
  • 41
  • 66
  • Thanks, the curl statement with all four headers continues to pass, so I'm going to double check that the https part is working in python and that it's not the path (it seems the same). Then try generating with the latest from master of swagger-codegen. – dlamblin Jan 04 '17 at 21:35
  • Thanks for identifying the right place to print out all the method, url, fields, and headers. Having done that I realized that there's two public end points to the api, and only one worked correctly, so I now get the exactly same, and correct behavior. More documentation would be nice to have, but in this case, it was an issue of debugging correctly. – dlamblin Jan 05 '17 at 00:42
2

In your spec, you will need to describe the security setting (API key in your case) similar to this example defined in the securityDefinitions section of the OpenAPI/Swagger spec.

Then in the endpoint, you will need to "apply" the security definition similar to this example

After that, you can set the API key in configuration.py of the auto-generated Python API client and the HTTP request will include the API key accordingly (either in the header or URL query string as defined in the security setting of the spec)

Since the last stable release of Swagger Codegen, there has been quite a lot of enhancements to the auto-generated Python API client so I would suggest you to pull the latest master and build the JAR locally to generate API clients.

William Cheng
  • 10,137
  • 5
  • 54
  • 79
  • This is useful additional details. I noted in my update that I did update the spec as per both examples. I did not modify the configuration.py because I do not want to commit the key in the code; the operator in question gets it from a connection in airflow. But in testing it I used the API directly, so I'll try that too. I can also try the latest from master too (I expected the editor.swagger.io to be up to date). – dlamblin Jan 04 '17 at 21:29