Skip to content

Commit

Permalink
Merge branch 'master' into content_type_validation
Browse files Browse the repository at this point in the history
  • Loading branch information
dtkav authored Nov 12, 2018
2 parents d8429d6 + 3436434 commit 29342ff
Show file tree
Hide file tree
Showing 18 changed files with 434 additions and 77 deletions.
83 changes: 60 additions & 23 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ In this example, Connexion automatically recognizes that your view
function expects an argument named ``message`` and assigns the value
of the endpoint parameter ``message`` to your view function.

.. note:: In the OpenAPI 3.x.x spec, the requestBody does not have a name.
By default it will be passed in as 'body'. You can optionally
provide the x-body-name parameter in your requestBody schema
to override the name of the parameter that will be passed to your
handler function.

.. warning:: When you define a parameter at your endpoint as *not* required, and
this argument does not have default value in your Python view, you will get
a "missing positional argument" exception whenever you call this endpoint
Expand All @@ -278,7 +284,7 @@ do type casting to related Python native values. The current
available type castings are:

+--------------+-------------+
| Swagger Type | Python Type |
| OpenAPI Type | Python Type |
+==============+=============+
| integer | int |
+--------------+-------------+
Expand All @@ -290,6 +296,8 @@ available type castings are:
+--------------+-------------+
| array | list |
+--------------+-------------+
| null | None |
+--------------+-------------+
| object | dict |
+--------------+-------------+

Expand All @@ -299,30 +307,35 @@ supports collection formats "pipes" and "csv". The default format is "csv".

Connexion is opinionated about how the URI is parsed for ``array`` types.
The default behavior for query parameters that have been defined multiple
times is to join them all together. For example, if you provide a URI with
times is to use the right-most value. For example, if you provide a URI with
the the query string ``?letters=a,b,c&letters=d,e,f``, connexion will set
``letters = ['a', 'b', 'c', 'd', 'e', 'f']``.
``letters = ['d', 'e', 'f']``.

You can override this behavior by specifying the URI parser in the app or
api options.

.. code-block:: python
from connexion.decorators.uri_parsing import Swagger2URIParser
options = {'uri_parsing_class': Swagger2URIParser}
options = {'uri_parsing_class': AlwaysMultiURIParser}
app = connexion.App(__name__, specification_dir='swagger/', options=options)
You can implement your own URI parsing behavior by inheriting from
``connextion.decorators.uri_parsing.AbstractURIParser``.

There are three URI parsers included with connection.
There are a handful of URI parsers included with connection.

+----------------------+---------------------------------------------------------------------------+
| AlwaysMultiURIParser | This parser is backwards compatible, and joins together multiple instances|
| (default) | of the same query parameter. |
| OpenAPIURIParser | This parser adheres to the OpenAPI 3.x.x spec, and uses the ``style`` |
| default: OpenAPI 3.0 | parameter. Query parameters are parsed from left to right, so if a query |
| | parameter is defined twice, then the right-most definition will take |
| | precedence. For example, if you provided a URI with the query string |
| | ``?letters=a,b,c&letters=d,e,f``, and ``style: simple``, then connexion |
| | will set ``letters = ['d', 'e', 'f']``. For additional information see |
| | `OpenAPI 3.0 Style Values`_. |
+----------------------+---------------------------------------------------------------------------+
| Swagger2URIParser | This parser adheres to the Swagger 2.0 spec, and will only join together |
| | multiple instance of the same query parameter if the ``collectionFormat`` |
| default: OpenAPI 2.0 | multiple instance of the same query parameter if the ``collectionFormat`` |
| | is set to ``multi``. Query parameters are parsed from left to right, so |
| | if a query parameter is defined twice, then the right-most definition |
| | wins. For example, if you provided a URI with the query string |
Expand All @@ -334,6 +347,9 @@ There are three URI parsers included with connection.
| | string ``?letters=a,b,c&letters=d,e,f`` and ``collectionFormat: csv`` |
| | hen connexion will set ``letters = ['a', 'b', 'c']`` |
+----------------------+---------------------------------------------------------------------------+
| AlwaysMultiURIParser | This parser is backwards compatible with Connexion 1.x. It joins together |
| | multiple instances of the same query parameter. |
+----------------------+---------------------------------------------------------------------------+


Parameter validation
Expand All @@ -351,18 +367,33 @@ to your application:
API Versioning and basePath
---------------------------

You can also define a ``basePath`` on the top level of the API
specification. This is useful for versioned APIs. To serve the
previous endpoint from ``http://MYHOST/1.0/hello_world``, type:
Setting a base path is useful for versioned APIs. An example of
a base path would be the ``1.0`` in ``http://MYHOST/1.0/hello_world``.

If you are using OpenAPI 3.x.x, you set your base URL path in the
servers block of the specification. You can either specify a full
URL, or just a relative path.

.. code-block:: yaml
servers:
- url: https://MYHOST/1.0
description: full url example
- url: /1.0
description: relative path example
paths:
...
If you are using OpenAPI 2.0, you can define a ``basePath`` on the top level
of your OpenAPI 2.0 specification.

.. code-block:: yaml
basePath: /1.0
paths:
/hello_world:
post:
operationId: myapp.api.hello_world
...
If you don't want to include the base path in your specification, you
can provide it when adding the API to your application:
Expand All @@ -374,22 +405,27 @@ can provide it when adding the API to your application:
Swagger JSON
------------
Connexion makes the OpenAPI/Swagger specification in JSON format
available from ``swagger.json`` in the base path of the API.
available from either ``swagger.json`` (for OpenAPI 2.0) or
``openapi.json`` (for OpenAPI 3.x.x) at the base path of the API.
For example, if your base path was ``1.0``, then your spec would be
available at ``/1.0/openapi.json``.

You can disable the Swagger JSON at the application level:
You can disable serving the spec JSON at the application level:

.. code-block:: python
app = connexion.App(__name__, specification_dir='swagger/',
swagger_json=False)
options = {"serve_spec": False}
app = connexion.App(__name__, specification_dir='openapi/',
options=options)
app.add_api('my_api.yaml')
You can also disable it at the API level:

.. code-block:: python
app = connexion.App(__name__, specification_dir='swagger/')
app.add_api('my_api.yaml', swagger_json=False)
options = {"serve_spec": False}
app = connexion.App(__name__, specification_dir='openapi/')
app.add_api('my_api.yaml', options=options)
HTTPS Support
-------------
Expand Down Expand Up @@ -428,7 +464,7 @@ You can disable the Swagger UI at the application level:

.. code-block:: python
app = connexion.App(__name__, specification_dir='swagger/',
app = connexion.App(__name__, specification_dir='openapi/',
options={"swagger_ui": False})
app.add_api('my_api.yaml')
Expand All @@ -437,7 +473,7 @@ You can also disable it at the API level:

.. code-block:: python
app = connexion.App(__name__, specification_dir='swagger/')
app = connexion.App(__name__, specification_dir='openapi/')
app.add_api('my_api.yaml', options={"swagger_ui": False})
If necessary, you can explicitly specify the path to the directory with
Expand All @@ -447,7 +483,7 @@ In order to do this, you should specify the following option:
.. code-block:: python
options = {'swagger_path': '/path/to/swagger_ui/'}
app = connexion.App(__name__, specification_dir='swagger/', options=options)
app = connexion.App(__name__, specification_dir='openapi/', options=options)
If you wish to provide your own swagger-ui distro, note that connextion
expects a jinja2 file called ``swagger_ui/index.j2`` in order to load the
Expand Down Expand Up @@ -568,6 +604,7 @@ Unless required by applicable law or agreed to in writing, software distributed
.. _Jinja2: http://jinja.pocoo.org/
.. _rfc6750: https://tools.ietf.org/html/rfc6750
.. _OpenAPI Specification: https://www.openapis.org/
.. _OpenAPI 3.0 Style Values: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#style-values
.. _Operation Object: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#operation-object
.. _swager.spec.security_definition: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-definitions-object
.. _swager.spec.security_requirement: https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md#security-requirement-object
Expand Down
File renamed without changes.
70 changes: 54 additions & 16 deletions connexion/decorators/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,21 @@ def get_apikeyinfo_func(security_definition):
return None


def get_bearerinfo_func(security_definition):
"""
:type security_definition: dict
:rtype: function
>>> get_bearerinfo_func({'x-bearerInfoFunc': 'foo.bar'})
'<function foo.bar>'
"""
func = (security_definition.get("x-bearerInfoFunc") or
os.environ.get('BEARERINFO_FUNC'))
if func:
return get_function_from_name(func)
return None


def security_passthrough(function):
"""
:type function: types.FunctionType
Expand Down Expand Up @@ -137,26 +152,39 @@ def validate_scope(required_scopes, token_scopes):
return True


def verify_oauth(token_info_func, scope_validate_func):
def wrapper(request, required_scopes):
authorization = request.headers.get('Authorization')
if not authorization:
return None
def verify_authorization_token(request, token_info_func):
"""
:param request: ConnexionRequest
:param token_info_func: types.FunctionType
:rtype: dict
"""
authorization = request.headers.get('Authorization')
if not authorization:
return None

try:
auth_type, token = authorization.split(None, 1)
except ValueError:
raise OAuthProblem(description='Invalid authorization header')
try:
auth_type, token = authorization.split(None, 1)
except ValueError:
raise OAuthProblem(description='Invalid authorization header')

if auth_type.lower() != 'bearer':
return None
if auth_type.lower() != 'bearer':
return None

token_info = token_info_func(token)
if token_info is None:
raise OAuthResponseProblem(
description='Provided token is not valid',
token_response=None
)

token_info = token_info_func(token)
return token_info


def verify_oauth(token_info_func, scope_validate_func):
def wrapper(request, required_scopes):
token_info = verify_authorization_token(request, token_info_func)
if token_info is None:
raise OAuthResponseProblem(
description='Provided oauth token is not valid',
token_response=None
)
return None

# Fallback to 'scopes' for backward compability
token_scopes = token_info.get('scope', token_info.get('scopes', ''))
Expand Down Expand Up @@ -222,6 +250,16 @@ def wrapper(request, required_scopes):
return wrapper


def verify_bearer(bearer_info_func):
"""
:param bearer_info_func: types.FunctionType
:rtype: types.FunctionType
"""
def wrapper(request, required_scopes):
return verify_authorization_token(request, bearer_info_func)
return wrapper


def verify_security(auth_funcs, required_scopes, function):
@functools.wraps(function)
def wrapper(request):
Expand Down
29 changes: 22 additions & 7 deletions connexion/operations/secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
from ..decorators.decorator import (BeginOfRequestLifecycleDecorator,
EndOfRequestLifecycleDecorator)
from ..decorators.security import (get_apikeyinfo_func, get_basicinfo_func,
get_bearerinfo_func,
get_scope_validate_func, get_tokeninfo_func,
security_deny, security_passthrough,
verify_apikey, verify_basic, verify_oauth,
verify_security)
verify_apikey, verify_basic, verify_bearer,
verify_oauth, verify_security)

logger = logging.getLogger("connexion.operations.secure")

Expand Down Expand Up @@ -118,16 +119,30 @@ def security_decorator(self):
continue

auth_funcs.append(verify_basic(basic_info_func))
elif scheme == 'bearer':
bearer_info_func = get_bearerinfo_func(security_scheme)
if not bearer_info_func:
logger.warning("... x-bearerInfoFunc missing", extra=vars(self))
continue
auth_funcs.append(verify_bearer(bearer_info_func))
else:
logger.warning("... Unsupported http authorization scheme %s" % scheme, extra=vars(self))

elif security_scheme['type'] == 'apiKey':
apikey_info_func = get_apikeyinfo_func(security_scheme)
if not apikey_info_func:
logger.warning("... x-apikeyInfoFunc missing", extra=vars(self))
continue
scheme = security_scheme.get('x-authentication-scheme', '').lower()
if scheme == 'bearer':
bearer_info_func = get_bearerinfo_func(security_scheme)
if not bearer_info_func:
logger.warning("... x-bearerInfoFunc missing", extra=vars(self))
continue
auth_funcs.append(verify_bearer(bearer_info_func))
else:
apikey_info_func = get_apikeyinfo_func(security_scheme)
if not apikey_info_func:
logger.warning("... x-apikeyInfoFunc missing", extra=vars(self))
continue

auth_funcs.append(verify_apikey(apikey_info_func, security_scheme['in'], security_scheme['name']))
auth_funcs.append(verify_apikey(apikey_info_func, security_scheme['in'], security_scheme['name']))

else:
logger.warning("... Unsupported security scheme type %s" % security_scheme['type'], extra=vars(self))
Expand Down
4 changes: 2 additions & 2 deletions docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ by the Connexion payload validation on request parameters and response
payloads of your API.

Let's say your API deals with Products and you want to define a field
`price_label` that have a "money" format value. You can create a format
`price_label` that has a "money" format value. You can create a format
checker function and register that to be used to validate values of
"money" format.
the "money" format.

Example of a possible schema of Product having an attribute with
"money" format that would be defined in your OpenAPI specification:
Expand Down
6 changes: 3 additions & 3 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Welcome to Connexion's documentation!
=====================================

Connexion is a framework on top of Flask_ that automagically handles
HTTP requests based on `OpenAPI 2.0 Specification`_ (formerly known as
Swagger Spec) of your API described in `YAML format`_. Connexion
HTTP requests based on either the `OpenAPI 2.0 Specification`_ (formerly known
as Swagger Spec) or the `OpenAPI 3.0 Specification`_. Connexion
allows you to write a Swagger specification and then maps the
endpoints to your Python functions. This is what makes it unique from
other tools that generate the specification based on your Python
Expand All @@ -35,4 +35,4 @@ Contents:

.. _Flask: http://flask.pocoo.org/
.. _OpenAPI 2.0 Specification: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md
.. _YAML format: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#format
.. _OpenAPI 3.0 Specification: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md
Loading

0 comments on commit 29342ff

Please sign in to comment.