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

[Python] Python HTTP signature update #5154

Merged
merged 11 commits into from
Feb 3, 2020
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,12 @@ private Map<String, Object> buildSupportFileBundle(List<Object> allOperations, L
if (ProcessUtils.hasHttpSignatureMethods(authMethods)) {
bundle.put("hasHttpSignatureMethods", true);
}
if (ProcessUtils.hasHttpBasicMethods(authMethods)) {
bundle.put("hasHttpBasicMethods", true);
}
if (ProcessUtils.hasApiKeyMethods(authMethods)) {
bundle.put("hasApiKeyMethods", true);
}
}

List<CodegenServer> servers = config.fromServers(openAPI.getServers());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,48 @@ public static boolean hasBearerMethods(Map<String, Object> objs) {
return false;
}

/**
* Returns true if the specified OAS model has at least one operation with the HTTP basic
* security scheme.
*
* @param authMethods List of auth methods.
* @return True if at least one operation has HTTP basic security scheme defined
*/
public static boolean hasHttpBasicMethods(List<CodegenSecurity> authMethods) {
if (authMethods != null && !authMethods.isEmpty()) {
for (CodegenSecurity cs : authMethods) {
if (Boolean.TRUE.equals(cs.isBasicBasic)) {
return true;
}
}
}
return false;
}

/**
* Returns true if the specified OAS model has at least one operation with API keys.
*
* @param authMethods List of auth methods.
* @return True if at least one operation has API key security scheme defined
*/
public static boolean hasApiKeyMethods(List<CodegenSecurity> authMethods) {
if (authMethods != null && !authMethods.isEmpty()) {
for (CodegenSecurity cs : authMethods) {
if (Boolean.TRUE.equals(cs.isApiKey)) {
return true;
}
}
}
return false;
}

/**
* Returns true if the specified OAS model has at least one operation with the HTTP signature
* security scheme.
* The HTTP signature scheme is defined in https://datatracker.ietf.org/doc/draft-cavage-http-signatures/
*
* @param authMethods List of auth methods.
* @return True if at least one operation has HTTP signature security schema defined
* @return True if at least one operation has HTTP signature security scheme defined
*/
public static boolean hasHttpSignatureMethods(List<CodegenSecurity> authMethods) {
if (authMethods != null && !authMethods.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,16 @@ class Configuration(object):
The dict value is an API key prefix when generating the auth data.
:param username: Username for HTTP basic authentication
:param password: Password for HTTP basic authentication
:param signing_info: Configuration parameters for HTTP signature.
{{#hasHttpSignatureMethods}}
:param signing_info: Configuration parameters for the HTTP signature security scheme.
Must be an instance of {{{packageName}}}.signing.HttpSigningConfiguration
{{/hasHttpSignatureMethods}}

{{#hasAuthMethods}}
:Example:
{{#hasApiKeyMethods}}

API Key Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
Expand All @@ -51,12 +56,32 @@ class Configuration(object):
)
The following cookie will be added to the HTTP request:
Cookie: JSESSIONID abc123
{{/hasApiKeyMethods}}
{{#hasHttpBasicMethods}}

HTTP Basic Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
http_basic_auth:
type: http
scheme: basic

Configure API client with HTTP basic authentication:
conf = {{{packageName}}}.Configuration(
username='the-user',
password='the-password',
)
{{/hasHttpBasicMethods}}
{{#hasHttpSignatureMethods}}

HTTP Signature Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
http_basic_auth:
type: http
scheme: signature

Configure API client with HTTP signature authentication. Use the 'hs2019' signature scheme,
sign the HTTP requests with the RSA-SSA-PSS signature algorithm, and set the expiration time
Expand All @@ -83,18 +108,22 @@ class Configuration(object):
signing.HEADER_DATE,
signing.HEADER_DIGEST,
'Content-Type',
'Content-Length',
'User-Agent'
],
signature_max_validity = datetime.timedelta(minutes=5)
)
)
{{/hasHttpSignatureMethods}}
{{/hasAuthMethods}}
"""

def __init__(self, host="{{{basePath}}}",
api_key=None, api_key_prefix=None,
username=None, password=None,
signing_info=None):
{{#hasHttpSignatureMethods}}
signing_info=None,
{{/hasHttpSignatureMethods}}
):
"""Constructor
"""
self.host = host
Expand Down Expand Up @@ -123,19 +152,21 @@ class Configuration(object):
self.password = password
"""Password for HTTP basic authentication
"""
{{#hasHttpSignatureMethods}}
if signing_info is not None:
signing_info.host = host
self.signing_info = signing_info
"""The HTTP signing configuration
"""
{{/hasHttpSignatureMethods}}
{{#hasOAuthMethods}}
self.access_token = ""
self.access_token = None
sebastien-rosset marked this conversation as resolved.
Show resolved Hide resolved
"""access token for OAuth/Bearer
"""
{{/hasOAuthMethods}}
{{^hasOAuthMethods}}
{{#hasBearerMethods}}
self.access_token = ""
self.access_token = None
sebastien-rosset marked this conversation as resolved.
Show resolved Hide resolved
"""access token for OAuth/Bearer
"""
{{/hasBearerMethods}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ from {{packageName}}.api_client import ApiClient

# import Configuration
from {{packageName}}.configuration import Configuration
{{#hasHttpSignatureMethods}}
from {{packageName}}.signing import HttpSigningConfiguration
{{/hasHttpSignatureMethods}}

# import exceptions
from {{packageName}}.exceptions import OpenApiException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,35 @@ import re
from six.moves.urllib.parse import urlencode, urlparse
from time import mktime

# The constants below define a subset of HTTP headers that can be included in the
# HTTP signature scheme. Additional headers may be included in the signature.

# The '(request-target)' header is a calculated field that includes the HTTP verb,
# the URL path and the URL query.
HEADER_REQUEST_TARGET = '(request-target)'
# The time when the HTTP signature was generated.
HEADER_CREATED = '(created)'
# The time when the HTTP signature expires. The API server should reject HTTP requests
# that have expired.
HEADER_EXPIRES = '(expires)'
# The 'Host' header.
HEADER_HOST = 'Host'
# The 'Date' header.
HEADER_DATE = 'Date'
HEADER_DIGEST = 'Digest' # RFC 3230, include digest of the HTTP request body.
# When the 'Digest' header is included in the HTTP signature, the client automatically
# computes the digest of the HTTP request body, per RFC 3230.
HEADER_DIGEST = 'Digest'
# The 'Authorization' header is automatically generated by the client. It includes
# the list of signed headers and a base64-encoded signature.
HEADER_AUTHORIZATION = 'Authorization'

# The constants below define the cryptographic schemes for the HTTP signature scheme.
SCHEME_HS2019 = 'hs2019'
SCHEME_RSA_SHA256 = 'rsa-sha256'
SCHEME_RSA_SHA512 = 'rsa-sha512'

# The constants below define the signature algorithms that can be used for the HTTP
# signature scheme.
ALGORITHM_RSASSA_PSS = 'RSASSA-PSS'
ALGORITHM_RSASSA_PKCS1v15 = 'RSASSA-PKCS1-v1_5'

Expand Down Expand Up @@ -364,5 +381,4 @@ class HttpSigningConfiguration(object):
auth_str = auth_str + "headers=\"{0}\",signature=\"{1}\"".format(
headers_value, signed_msg.decode('ascii'))

print("AUTH: {0}".format(auth_str))
return auth_str
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ class Configuration(object):
The dict value is an API key prefix when generating the auth data.
:param username: Username for HTTP basic authentication
:param password: Password for HTTP basic authentication
:param signing_info: Configuration parameters for HTTP signature.
Must be an instance of petstore_api.signing.HttpSigningConfiguration

:Example:

API Key Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
Expand All @@ -57,49 +56,25 @@ class Configuration(object):
The following cookie will be added to the HTTP request:
Cookie: JSESSIONID abc123

HTTP Basic Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
http_basic_auth:
type: http
scheme: basic

Configure API client with HTTP basic authentication:
conf = petstore_api.Configuration(
username='the-user',
password='the-password',
)

Configure API client with HTTP signature authentication. Use the 'hs2019' signature scheme,
sign the HTTP requests with the RSA-SSA-PSS signature algorithm, and set the expiration time
of the signature to 5 minutes after the signature has been created.
Note you can use the constants defined in the petstore_api.signing module, and you can
also specify arbitrary HTTP headers to be included in the HTTP signature, except for the
'Authorization' header, which is used to carry the signature.

One may be tempted to sign all headers by default, but in practice it rarely works.
This is beccause explicit proxies, transparent proxies, TLS termination endpoints or
load balancers may add/modify/remove headers. Include the HTTP headers that you know
are not going to be modified in transit.

conf = petstore_api.Configuration(
signing_info = petstore_api.signing.HttpSigningConfiguration(
key_id = 'my-key-id',
private_key_path = 'rsa.pem',
signing_scheme = signing.SCHEME_HS2019,
signing_algorithm = signing.ALGORITHM_RSASSA_PSS,
signed_headers = [signing.HEADER_REQUEST_TARGET,
signing.HEADER_CREATED,
signing.HEADER_EXPIRES,
signing.HEADER_HOST,
signing.HEADER_DATE,
signing.HEADER_DIGEST,
'Content-Type',
'Content-Length',
'User-Agent'
],
signature_max_validity = datetime.timedelta(minutes=5)
)
)
"""

def __init__(self, host="http://petstore.swagger.io:80/v2",
api_key=None, api_key_prefix=None,
username=None, password=None,
signing_info=None):
):
"""Constructor
"""
self.host = host
Expand Down Expand Up @@ -128,12 +103,7 @@ def __init__(self, host="http://petstore.swagger.io:80/v2",
self.password = password
"""Password for HTTP basic authentication
"""
if signing_info is not None:
signing_info.host = host
self.signing_info = signing_info
"""The HTTP signing configuration
"""
self.access_token = ""
self.access_token = None
"""access token for OAuth/Bearer
"""
self.logger = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,10 @@ class Configuration(object):
The dict value is an API key prefix when generating the auth data.
:param username: Username for HTTP basic authentication
:param password: Password for HTTP basic authentication
:param signing_info: Configuration parameters for HTTP signature.
Must be an instance of petstore_api.signing.HttpSigningConfiguration

:Example:

API Key Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
Expand All @@ -58,49 +57,25 @@ class Configuration(object):
The following cookie will be added to the HTTP request:
Cookie: JSESSIONID abc123

HTTP Basic Authentication Example.
Given the following security scheme in the OpenAPI specification:
components:
securitySchemes:
http_basic_auth:
type: http
scheme: basic

Configure API client with HTTP basic authentication:
conf = petstore_api.Configuration(
username='the-user',
password='the-password',
)

Configure API client with HTTP signature authentication. Use the 'hs2019' signature scheme,
sign the HTTP requests with the RSA-SSA-PSS signature algorithm, and set the expiration time
of the signature to 5 minutes after the signature has been created.
Note you can use the constants defined in the petstore_api.signing module, and you can
also specify arbitrary HTTP headers to be included in the HTTP signature, except for the
'Authorization' header, which is used to carry the signature.

One may be tempted to sign all headers by default, but in practice it rarely works.
This is beccause explicit proxies, transparent proxies, TLS termination endpoints or
load balancers may add/modify/remove headers. Include the HTTP headers that you know
are not going to be modified in transit.

conf = petstore_api.Configuration(
signing_info = petstore_api.signing.HttpSigningConfiguration(
key_id = 'my-key-id',
private_key_path = 'rsa.pem',
signing_scheme = signing.SCHEME_HS2019,
signing_algorithm = signing.ALGORITHM_RSASSA_PSS,
signed_headers = [signing.HEADER_REQUEST_TARGET,
signing.HEADER_CREATED,
signing.HEADER_EXPIRES,
signing.HEADER_HOST,
signing.HEADER_DATE,
signing.HEADER_DIGEST,
'Content-Type',
'Content-Length',
'User-Agent'
],
signature_max_validity = datetime.timedelta(minutes=5)
)
)
"""

def __init__(self, host="http://petstore.swagger.io:80/v2",
api_key=None, api_key_prefix=None,
username=None, password=None,
signing_info=None):
):
"""Constructor
"""
self.host = host
Expand Down Expand Up @@ -129,12 +104,7 @@ def __init__(self, host="http://petstore.swagger.io:80/v2",
self.password = password
"""Password for HTTP basic authentication
"""
if signing_info is not None:
signing_info.host = host
self.signing_info = signing_info
"""The HTTP signing configuration
"""
self.access_token = ""
self.access_token = None
"""access token for OAuth/Bearer
"""
self.logger = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ def test_config(self):
self.assertIsNotNone(config.get_host_settings())
self.assertEqual(config.get_basic_auth_token(),
urllib3.util.make_headers(basic_auth=":").get('authorization'))
# No authentication scheme has been configured at this point, so auth_settings()
# should return an empty list.
self.assertEqual(len(config.auth_settings()), 0)
# Configure OAuth2 access token and verify the auth_settings have OAuth2 parameters.
config.access_token = 'MY-ACCESS_TOKEN'
self.assertEqual(len(config.auth_settings()), 1)
self.assertIn("petstore_auth", config.auth_settings().keys())
config.username = "user"
Expand Down
Loading