Skip to content

Commit

Permalink
Merge pull request #147 from zalando/146-tokeninfo-url
Browse files Browse the repository at this point in the history
#146 allow using TOKENINFO_URL env var (OAuth2 token info)
  • Loading branch information
rafaelcaricio committed Feb 13, 2016
2 parents 43cc1cf + 91f2f4b commit c40aabc
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -8,3 +8,4 @@ htmlcov/
*.pyc
.eggs
.cache/
*.swp
2 changes: 1 addition & 1 deletion README.rst
Expand Up @@ -147,7 +147,7 @@ Authentication and Authorization
If the specification includes a Oauth2 `Security Definition <swager.spec.security_definition_>`_ compatible with the
Zalando Greendale Team's infrastructure Connexion will automatically handle token validation and authorization for
operations that have `Security Requirements <swager.spec.security_requirement_>`_. One main difference between the usual
OAuth flow and the one Connexion uses is that the API Security Definition **must** include a 'x-tokenInfoUrl' (or set ``HTTP_TOKENINFO_URL`` env var) with the
OAuth flow and the one Connexion uses is that the API Security Definition **must** include a 'x-tokenInfoUrl' (or set ``TOKENINFO_URL`` env var) with the
URL to use to validate and get the token information.
Connexion expects to receive the Oauth token in the ``Authorization`` header field in the format described in
`RFC 6750 <rfc6750_>`_ section 2.1.
Expand Down
18 changes: 18 additions & 0 deletions connexion/decorators/security.py
Expand Up @@ -16,6 +16,7 @@
from flask import request
import functools
import logging
import os
import requests
from ..problem import problem

Expand All @@ -28,6 +29,23 @@
session.mount('https://', adapter)


def get_tokeninfo_url(security_definition):
'''
:type security_definition: dict
:rtype: str
>>> get_tokeninfo_url({'x-tokenInfoUrl': 'foo'})
'foo'
'''
token_info_url = security_definition.get('x-tokenInfoUrl') or \
os.environ.get('HTTP_TOKENINFO_URL') or \
os.environ.get('TOKENINFO_URL')
# https://github.com/zalando/connexion/issues/148
if token_info_url and token_info_url == os.environ.get('HTTP_TOKENINFO_URL'):
logger.warn('The HTTP_TOKENINFO_URL environment variable is deprecated. Please use TOKENINFO_URL instead.')
return token_info_url


def security_passthrough(function):
"""
:type function: types.FunctionType
Expand Down
5 changes: 2 additions & 3 deletions connexion/operation.py
Expand Up @@ -14,7 +14,6 @@
from copy import deepcopy
import functools
import logging
import os

import jsonschema
from jsonschema import ValidationError
Expand All @@ -24,7 +23,7 @@
from .decorators.parameter import parameter_to_arg
from .decorators.produces import BaseSerializer, Produces, Jsonifier
from .decorators.response import ResponseValidator
from .decorators.security import security_passthrough, verify_oauth
from .decorators.security import security_passthrough, verify_oauth, get_tokeninfo_url
from .decorators.validation import RequestBodyValidator, ParameterValidator, TypeValidationError
from .exceptions import InvalidSpecification
from .utils import flaskify_endpoint, produces_json
Expand Down Expand Up @@ -82,7 +81,7 @@ def security_decorator(self):
scheme_name, scopes = next(iter(security.items())) # type: str, list
security_definition = self.security_definitions[scheme_name]
if security_definition['type'] == 'oauth2':
token_info_url = security_definition.get('x-tokenInfoUrl', os.getenv('HTTP_TOKENINFO_URL'))
token_info_url = get_tokeninfo_url(security_definition)
if token_info_url:
scopes = set(scopes) # convert scopes to set because this is needed for verify_oauth
return functools.partial(verify_oauth, token_info_url, scopes)
Expand Down
37 changes: 37 additions & 0 deletions tests/decorators/test_security.py
@@ -0,0 +1,37 @@
from mock import MagicMock
from connexion.decorators.security import get_tokeninfo_url, verify_oauth
from connexion.problem import problem


def test_get_tokeninfo_url(monkeypatch):
env = {}
monkeypatch.setattr('os.environ', env)
logger = MagicMock()
monkeypatch.setattr('connexion.decorators.security.logger', logger)
security_def = {}
assert get_tokeninfo_url(security_def) is None
logger.warn.assert_not_called()
env['TOKENINFO_URL'] = 'issue-146'
assert get_tokeninfo_url(security_def) == 'issue-146'
logger.warn.assert_not_called()
env['HTTP_TOKENINFO_URL'] = 'foo'
assert get_tokeninfo_url(security_def) == 'foo'
logger.warn.assert_called_once_with('The HTTP_TOKENINFO_URL environment variable is deprecated. Please use TOKENINFO_URL instead.')
logger.warn.reset_mock()
security_def = {'x-tokenInfoUrl': 'bar'}
assert get_tokeninfo_url(security_def) == 'bar'
logger.warn.assert_not_called()


def test_verify_oauth_invalid_auth_header(monkeypatch):
def func():
pass

wrapped_func = verify_oauth('https://example.org/tokeninfo', set(['admin']), func)

request = MagicMock()
app = MagicMock()
monkeypatch.setattr('connexion.decorators.security.request', request)
monkeypatch.setattr('flask.current_app', app)
resp = wrapped_func()
assert resp == problem(401, 'Unauthorized', 'Invalid authorization header')

0 comments on commit c40aabc

Please sign in to comment.