Skip to content

Commit

Permalink
added new functionality and test coverage as described in #16 and #45
Browse files Browse the repository at this point in the history
  • Loading branch information
vltr committed Jan 19, 2018
1 parent cb2d82f commit e1e3d98
Show file tree
Hide file tree
Showing 18 changed files with 577 additions and 223 deletions.
18 changes: 18 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[paths]
source =
sanic_jwt
tests

[run]
source =
sanic_jwt
tests
branch = True

[report]
exclude_lines =
no cov
no qa
noqa
pragma: no cover
if __name__ == .__main__.:
7 changes: 7 additions & 0 deletions .isort.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[settings]
known_first_party=sanic_jwt
known_third_party=sanic
line_length=79
multi_line_output=3
not_skip=__init__.py
order_by_type=false
11 changes: 11 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[pytest]
addopts = -xrs --cov sanic_jwt --cov-report term-missing --pep8 --flakes --cache-clear
pep8ignore =
*.py E501 E402 E701
examples/*.py ALL
pep8maxlinelength = 79
testpaths =
tests
sanic_jwt
flakes-ignore =
examples/*.py ALL
21 changes: 10 additions & 11 deletions sanic_jwt/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import logging
logging.getLogger(__name__).addHandler(logging.NullHandler())

from sanic.response import text

from sanic_jwt.blueprint import bp as sanic_jwt_auth_bp
Expand All @@ -22,20 +25,16 @@ def initialize(

if class_views is not None:
for route, view in class_views:
try:
if issubclass(view, HTTPMethodView) and isinstance(route, str):
sanic_jwt_auth_bp.add_route(
view.as_view(),
route,
strict_slashes=app.config.SANIC_JWT_STRICT_SLASHES
)
else:
raise exceptions.InvalidClassViewsFormat()
except TypeError:
if issubclass(view, HTTPMethodView) and isinstance(route, str):
sanic_jwt_auth_bp.add_route(
view.as_view(),
route,
strict_slashes=app.config.SANIC_JWT_STRICT_SLASHES
)
else:
raise exceptions.InvalidClassViewsFormat()

# Add blueprint
# sanic_jwt_auth_bp.strict_slashes = app.strict_slashes
app.blueprint(sanic_jwt_auth_bp, url_prefix=app.config.SANIC_JWT_URL_PREFIX)

# Setup authentication module
Expand Down
44 changes: 23 additions & 21 deletions sanic_jwt/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ def __init__(self, app, authenticate):
self.claims = ['exp']

async def store_refresh_token(self, *args, **kwargs):
raise exceptions.RefreshTokenNotImplemented()
raise exceptions.RefreshTokenNotImplemented() # noqa

async def retrieve_refresh_token(self, *args, **kwargs):
raise exceptions.RefreshTokenNotImplemented()
raise exceptions.RefreshTokenNotImplemented() # noqa


class SanicJWTAuthentication(BaseAuthentication):
Expand Down Expand Up @@ -97,28 +97,30 @@ def _get_token(self, request, refresh_token=False):

if self.app.config.SANIC_JWT_COOKIE_SET:
token = request.cookies.get(cookie_token_name, None)
if token is None:
raise exceptions.MissingAuthorizationCookie()
return token
else:
header = request.headers.get(self.app.config.SANIC_JWT_AUTHORIZATION_HEADER, None)
if header:
try:
prefix, token = header.split(' ')
# if prefix != self.app.config.SANIC_JWT_AUTHORIZATION_HEADER_PREFIX:
if prefix != header_prefix:
raise Exception
except Exception:
raise exceptions.InvalidAuthorizationHeader()

if refresh_token:
token = request.json.get(self.app.config.SANIC_JWT_REFRESH_TOKEN_NAME)

if token:
return token
else:
if self.app.config.SANIC_JWT_COOKIE_STRICT:
raise exceptions.MissingAuthorizationCookie()

header = request.headers.get(self.app.config.SANIC_JWT_AUTHORIZATION_HEADER, None)
if header:
try:
prefix, token = header.split(' ')
# if prefix != self.app.config.SANIC_JWT_AUTHORIZATION_HEADER_PREFIX:
if prefix != header_prefix:
raise Exception
except Exception:
raise exceptions.InvalidAuthorizationHeader()

if refresh_token:
token = request.json.get(self.app.config.SANIC_JWT_REFRESH_TOKEN_NAME)

return token

raise exceptions.MissingAuthorizationHeader()
raise exceptions.MissingAuthorizationHeader()

def _get_refresh_token(self, request):
async def _get_refresh_token(self, request):
return self._get_token(request, refresh_token=True)

def _get_user_id(self, user):
Expand Down
12 changes: 6 additions & 6 deletions sanic_jwt/blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ async def setup_claims(app, *args, **kwargs):
async def authenticate(request, *args, **kwargs):
if request.method == 'OPTIONS':
return text('', status=204)
try:
user = await request.app.auth.authenticate(request, *args, **kwargs)
except Exception as e:
raise e
# try:
user = await request.app.auth.authenticate(request, *args, **kwargs)
# except Exception as e:
# raise e

access_token, output = await get_access_token_output(request, user)

Expand All @@ -70,7 +70,7 @@ async def retrieve_user(request, *args, **kwargs):

try:
payload = request.app.auth.extract_payload(request)
user = request.app.auth.retrieve_user(request, payload)
user = await request.app.auth.retrieve_user(request, payload)
except exceptions.MissingAuthorizationCookie:
user = None
payload = None
Expand Down Expand Up @@ -115,7 +115,7 @@ async def refresh(request, *args, **kwargs):
# TODO:
# - Add exceptions
payload = request.app.auth.extract_payload(request, verify=False)
user = request.app.auth.retrieve_user(request, payload=payload)
user = await request.app.auth.retrieve_user(request, payload=payload)
user_id = request.app.auth._get_user_id(user)
refresh_token = await request.app.auth.retrieve_refresh_token(request=request, user_id=user_id)
if isinstance(refresh_token, bytes):
Expand Down
2 changes: 1 addition & 1 deletion sanic_jwt/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
SANIC_JWT_URL_PREFIX = '/auth'
SANIC_JWT_USER_ID = 'user_id'
SANIC_JWT_VERIFY_EXP = True

SANIC_JWT_COOKIE_STRICT = True
SANIC_JWT_COOKIE_REFRESH_TOKEN_NAME = SANIC_JWT_REFRESH_TOKEN_NAME
22 changes: 0 additions & 22 deletions sanic_jwt/validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,27 +58,5 @@ async def validate_scopes(request, scopes, user_scopes, require_all=True, requir
)


# print(1, 'False', validate_single_scope('user', ['something']))
# print(2, 'True', validate_single_scope('user', ['user']))
# print(3, 'True', validate_single_scope('user:read', ['user']))
# print(4, 'True', validate_single_scope('user:read', ['user:read']))
# print(5, 'False', validate_single_scope('user:read', ['user:write']))
# print(6, 'True', validate_single_scope('user:read', ['user:read:write']))
# print(7, 'False', validate_single_scope('user', ['user:read']))
# print(8, 'False', validate_single_scope('user:read:write', ['user:read']))
# print(9, 'True', validate_single_scope('user:read:write', ['user:read:write']))
# print(10, 'True', validate_single_scope('user:read:write', ['user:write:read']))
# print(11, 'True', validate_single_scope('user:read:write', ['user:read'], False))


# print(12, 'False', validate_single_scope('user', ['something', 'else']))
# print(13, 'True', validate_single_scope('user', ['something', 'else', 'user']))
# print(14, 'True', validate_single_scope('user:read', ['something:else', 'user:read']))
# print(15, 'True', validate_single_scope('user:read', ['user:read', 'something:else']))

# print(16, 'True', validate_single_scope(':read', [':read']))
# print(17, 'True', validate_single_scope(':read', ['admin']))


# entity:matrixadmin:node<34> [':matrixadmin']
# print('True', validate_single_scope('entity:matrixadmin:node<34>', [':matrixadmin'], require_all_actions=False))
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
[aliases]
test=pytest

[metadata]
description-file = README.md
description-file = README.md
43 changes: 35 additions & 8 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

"""A setuptools based setup module.
See:
https://packaging.python.org/en/latest/distributing.html
Expand All @@ -17,14 +16,43 @@
# with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
# long_description = f.read()

tests_require = [
'coverage',
# 'pytest-cache',
'pytest-cov',
'pytest-flakes',
'pytest-pep8',
'pytest-sanic',
'pytest',
]

extras_require = {
'docs': [
# 'sphinx_rtd_theme',
'Sphinx',
],
'tests': tests_require,
}

extras_require['all'] = []
for reqs in extras_require.values():
extras_require['all'].extend(reqs)

setup_requires = [
'pytest-runner',
]

install_requires = [
'pyjwt',
]

setup(
name='sanic-jwt',

# Versions should comply with PEP440. For a discussion on single-sourcing
# the version across setup.py and the project code, see
# https://packaging.python.org/en/latest/single_source_version.html
version='0.4.1',

description='JWT oauth flow for Sanic',

# The project's main homepage.
Expand All @@ -47,7 +75,6 @@
'Development Status :: 4 - Beta',

# Indicate who your project is intended for

'Intended Audience :: Developers',
# 'Topic :: Software Development :: Build Tools',

Expand Down Expand Up @@ -76,16 +103,16 @@
# your project is installed. For an analysis of "install_requires" vs pip's
# requirements files see:
# https://packaging.python.org/en/latest/requirements.html
install_requires=['pyjwt'],
install_requires=install_requires,

# List additional groups of dependencies here (e.g. development
# dependencies). You can install these using the following syntax,
# for example:
# $ pip install -e .[dev,test]
extras_require={
# 'dev': ['check-manifest'],
# 'test': ['coverage'],
},
extras_require=extras_require,
setup_requires=setup_requires,
tests_require=tests_require,


# If there are data files included in your packages that need to be
# installed, specify them here. If using Python 2.6 or less, then these
Expand Down
80 changes: 80 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from sanic import Sanic
from sanic.response import json

import pytest
from sanic_jwt import exceptions, initialize
from sanic_jwt.decorators import protected


class User(object):

def __init__(self, id, username, password):
self.user_id = id
self.username = username
self.password = password

def __str__(self):
return "User(id='%s')" % self.id

def to_dict(self):
properties = [
'user_id',
'username',
]
return {prop: getattr(self, prop, None) for prop in properties}


@pytest.yield_fixture
def users():
yield [
User(1, 'user1', 'abcxyz'),
User(2, 'user2', 'abcxyz'),
]


@pytest.yield_fixture
def username_table(users):
yield {u.username: u for u in users}


@pytest.yield_fixture
def authenticate(username_table):
async def authenticate(request, *args, **kwargs):
username = request.json.get('username', None)
password = request.json.get('password', None)

if not username or not password:
raise exceptions.AuthenticationFailed(
"Missing username or password.")

user = username_table.get(username, None)
if user is None:
raise exceptions.AuthenticationFailed("User not found.")

if password != user.password:
raise exceptions.AuthenticationFailed("Password is incorrect.")

return user

yield authenticate


@pytest.yield_fixture
def app(username_table, authenticate):

sanic_app = Sanic()
initialize(
sanic_app,
authenticate=authenticate,
)

@sanic_app.route("/")
async def helloworld(request):
return json({"hello": "world"})

@sanic_app.route("/protected")
@protected()
async def protected_request(request):
return json({"protected": True})

yield sanic_app
Loading

0 comments on commit e1e3d98

Please sign in to comment.