Skip to content

Commit

Permalink
Update docu/examples
Browse files Browse the repository at this point in the history
  • Loading branch information
cziebuhr committed Sep 27, 2018
1 parent 450f3a5 commit 14ff115
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 93 deletions.
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ New in Connexion 2.0:
All spec validation errors should be wrapped with `InvalidSpecification`.
- Support for nullable/x-nullable, readOnly and writeOnly/x-writeOnly has been added to the standard json schema validator.
- Custom validators can now be specified on api level (instead of app level).
- If unsupported security requirements are defined or ``x-tokenInfoFunc``/``x-tokenInfoUrl`` is missing, connexion now denies requests instead of allowing access without security-check.

How to Use
==========
Expand Down
80 changes: 59 additions & 21 deletions docs/security.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,73 @@ Security
OAuth 2 Authentication and Authorization
----------------------------------------

Connexion supports one of the three OAuth 2 handling methods. (See
"TODO" below.) With Connexion, the API security definition **must**
include a 'x-tokenInfoUrl' or 'x-tokenInfoFunc (or set ``TOKENINFO_URL``
or ``TOKENINFO_FUNC`` env var respectively).
Connexion supports one of the three OAuth 2 handling methods.
With Connexion, the API security definition **must** include a
``x-tokenInfoFunc`` or set ``TOKENINFO_FUNC`` env var.

If 'x-tokenInfoFunc' is used, it must contain a reference to a function
``x-tokenInfoFunc`` must contain a reference to a function
used to obtain the token info. This reference should be a string using
the same syntax that is used to connect an ``operationId`` to a Python
function when routing. For example, an ``x-tokenInfoFunc`` of
``auth.verifyToken`` would pass the user's token string to the function
``verifyToken`` in the module ``auth.py``. The referenced function should
return a dict containing a ``scope`` or ``scopes`` field that is either
a space-separated list or an array of scopes belonging to the supplied
token. This list of scopes will be validated against the scopes required
by the API security definition to determine if the user is authorized.
``verifyToken`` in the module ``auth.py``. The referenced function accepts
a token string as argument and should return a dict containing a ``scope``
field that is either a space-separated list or an array of scopes belonging to
the supplied token. This list of scopes will be validated against the scopes
required by the API security definition to determine if the user is authorized.
You can supply a custom scope validation func with ``x-scopeValidateFunc``
or set ``SCOPEVALIDATE_FUNC`` env var, otherwise
``connexion.decorators.security.validate_scope`` will be used as default.

If 'x-tokenInfoUrl' is used, it must contain a URL to validate and get
the token information which complies with `RFC 6749 <rfc6749_>`_.

When both 'x-tokenInfoUrl' and 'x-tokenInfoFunc' are used, Connexion
will prioritize the function method. Connexion expects to receive the
OAuth token in the ``Authorization`` header field in the format
described in `RFC 6750 <rfc6750_>`_ section 2.1. This aspect represents
a significant difference from the usual OAuth flow.
The recommended approach is to return a dict which complies with
`RFC 7662 <rfc7662_>`_. Note that you have to validate the ``active``
or ``exp`` fields etc. yourself.

The ``uid`` property (username) of the Token Info response will be passed in the ``user`` argument to the handler function.
The ``sub`` property of the Token Info response will be passed in the ``user``
argument to the handler function.

You can find a `minimal OAuth example application`_ in Connexion's "examples" folder.

Deprecated features, retained for backward compability:

- As alternative to ``x-tokenInfoFunc``, you can set ``x-tokenInfoUrl`` or
``TOKENINFO_URL`` env var. It must contain a URL to validate and get the token
information which complies with `RFC 6749 <rfc6749_>`_.
When both ``x-tokenInfoUrl`` and ``x-tokenInfoFunc`` are used, Connexion
will prioritize the function method. Connexion expects the authorization
server to receive the OAuth token in the ``Authorization`` header field in the
format described in `RFC 6750 <rfc6750_>`_ section 2.1. This aspect represents
a significant difference from the usual OAuth flow.
- ``scope`` field can also be named ``scopes``.
- ``sub`` field can also be named ``uid``.

You can find a `minimal OAuth example application`_ in Connexion's "examples" folder.


Basic Authentication
--------------------

With Connexion, the API security definition **must** include a
``x-basicInfoFunc`` or set ``BASICINFO_FUNC`` env var. It uses the same
semantics as for ``x-tokenInfoFunc``, but the function accepts three
parameters: username, password and required_scopes. If the security declaration
of the operation also has an oauth security requirement, required_scopes is
taken from there, otherwise it's None. This allows authorizing individual
operations with oauth scope while using basic authentication for
authentication.

ApiKey Authentication
---------------------

With Connexion, the API security definition **must** include a
``x-apikeyInfoFunc`` or set ``APIKEYINFO_FUNC`` env var. It uses the same
semantics as for ``x-basicInfoFunc``, but the function accepts two
parameters: apikey and required_scopes.

You can find a `minimal Basic Auth example application`_ in Connexion's "examples" folder.


HTTPS Support
-------------

Expand All @@ -43,7 +81,7 @@ Swagger UI cannot be used to play with the API. What is the correct
way to start a HTTPS server when using Connexion?

.. _rfc6750: https://tools.ietf.org/html/rfc6750
.. _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
.. _rfc6749: https://tools.ietf.org/html/rfc6749
.. _minimal OAuth example application: https://github.com/zalando/connexion/tree/master/examples/oauth2
.. _rfc7662: https://tools.ietf.org/html/rfc7662
.. _minimal OAuth example application: https://github.com/zalando/connexion/tree/master/examples/swagger2/oauth2
.. _minimal Basic Auth example application: https://github.com/zalando/connexion/tree/master/examples/swagger2/basicauth
8 changes: 7 additions & 1 deletion examples/openapi3/basicauth/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ Running:
Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI.

The hardcoded credentials are ``admin`` and ``secret``.
The hardcoded credentials are ``admin`` and ``secret``. For an example with
correct authentication but missing access rights, use ``foo`` and ``bar``.

For a more simple example which doesn't use oauth scope for authorization see
the `Swagger2 Basic Auth example`_.

.. _Swagger2 Basic Auth example: https://github.com/zalando/connexion/tree/master/examples/swagger2/basicauth
57 changes: 23 additions & 34 deletions examples/openapi3/basicauth/app.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,38 @@
#!/usr/bin/env python3
'''
Connexion HTTP Basic Auth example
Most of the code stolen from http://flask.pocoo.org/snippets/8/
Warning: It is recommended to use 'decorator' package to create decorators for
your view functions to keep Connexion working as expected. For more
details please check: https://github.com/zalando/connexion/issues/142
Basic example of a resource server
'''

import connexion
import flask

try:
from decorator import decorator
except ImportError:
import sys
import logging
logging.error('Missing dependency. Please run `pip install decorator`')
sys.exit(1)

from connexion.decorators.security import validate_scope
from connexion.exceptions import OAuthScopeProblem

def check_auth(username: str, password: str):
'''This function is called to check if a username /
password combination is valid.'''
return username == 'admin' and password == 'secret'
def basic_auth(username, password, required_scopes=None):
if username == 'admin' and password == 'secret':
info = {'sub': 'admin', 'scope': 'secret' }
elif username == 'foo' and password == 'bar':
info = {'sub': 'user1', 'scope': '' }
else:
# optional: raise exception for custom error response
return None

# optional
if required_scopes is not None and not validate_scope(required_scopes, info['scope']):
raise OAuthScopeProblem(
description='Provided user doesn\'t have the required access rights',
required_scopes=required_scopes,
token_scopes=info['scope']
)

def authenticate():
'''Sends a 401 response that enables basic auth'''
return flask.Response('You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
return info


@decorator
def requires_auth(f: callable, *args, **kwargs):
auth = flask.request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
def dummy_func(token):
return None


@requires_auth
def get_secret() -> str:
return 'This is a very secret string requiring authentication!'
def get_secret(user) -> str:
return "You are {user} and the secret is 'wbevuec'".format(user=user)


if __name__ == '__main__':
Expand Down
10 changes: 10 additions & 0 deletions examples/openapi3/basicauth/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,19 @@ paths:
schema:
type: string
security:
- oauth2: ['secret']
- basic: []
components:
securitySchemes:
oauth2:
type: oauth2
x-tokenInfoFunc: app.dummy_func
flows:
implicit:
authorizationUrl: https://example.com/oauth2/dialog
scopes:
secret: Allow accessing secret
basic:
type: http
scheme: basic
x-basicInfoFunc: app.basic_auth
7 changes: 6 additions & 1 deletion examples/swagger2/basicauth/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ Running:

.. code-block:: bash
$ sudo pip3 install --upgrade connexion # install Connexion from PyPI
$ sudo pip3 install --upgrade connexion[swagger-ui] # install Connexion from PyPI
$ ./app.py
Now open your browser and go to http://localhost:8080/ui/ to see the Swagger UI.

The hardcoded credentials are ``admin`` and ``secret``.

For a more advanced example which reuses oauth scope for authorization see
the `OpenAPI3 Basic Auth example`_.

.. _OpenAPI3 Basic Auth example: https://github.com/zalando/connexion/tree/master/examples/openapi3/basicauth
44 changes: 8 additions & 36 deletions examples/swagger2/basicauth/app.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,21 @@
#!/usr/bin/env python3
'''
Connexion HTTP Basic Auth example
Most of the code stolen from http://flask.pocoo.org/snippets/8/
Warning: It is recommended to use 'decorator' package to create decorators for
your view functions to keep Connexion working as expected. For more
details please check: https://github.com/zalando/connexion/issues/142
Basic example of a resource server
'''

import connexion
import flask

try:
from decorator import decorator
except ImportError:
import sys
import logging
logging.error('Missing dependency. Please run `pip install decorator`')
sys.exit(1)


def check_auth(username: str, password: str):
'''This function is called to check if a username /
password combination is valid.'''
return username == 'admin' and password == 'secret'


def authenticate():
'''Sends a 401 response that enables basic auth'''
return flask.Response('You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def basic_auth(username, password, required_scopes=None):
if username == 'admin' and password == 'secret':
return {'sub': 'admin'}

# optional: raise exception for custom error response
return None

@decorator
def requires_auth(f: callable, *args, **kwargs):
auth = flask.request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)

def get_secret(user) -> str:
return "You are {user} and the secret is 'wbevuec'".format(user=user)

@requires_auth
def get_secret() -> str:
return 'This is a very secret string requiring authentication!'

if __name__ == '__main__':
app = connexion.FlaskApp(__name__)
Expand Down
1 change: 1 addition & 0 deletions examples/swagger2/basicauth/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ paths:
securityDefinitions:
basic:
type: basic
x-basicInfoFunc: app.basic_auth

0 comments on commit 14ff115

Please sign in to comment.