Skip to content
This repository has been archived by the owner on Apr 9, 2023. It is now read-only.

Commit

Permalink
Merge pull request #26 from plone/auth-rework2
Browse files Browse the repository at this point in the history
Implement basic jwt validator
  • Loading branch information
bloodbare committed Nov 23, 2016
2 parents d08e26d + 8351eef commit b94e65f
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 64 deletions.
5 changes: 4 additions & 1 deletion src/plone.server/CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
1.0a7 (unreleased)
------------------

- add jwt token validator
[vangheem]

- Add to finalize an AsyncUtil when its finishing the software
[ramon]

- Remove `AUTH_USER_PLUGINS` and `AUTH_EXTRACTION_PLUGINS`. Authentication now
consists of auth policies, user identifiers and token checkers.
consists of auth extractors, user identifiers and token validators.
[vangheem]

- Correctly check parent object for allowed addable types
Expand Down
17 changes: 11 additions & 6 deletions src/plone.server/plone/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@
'id': 'admin',
'password': ''
},
'auth_policies': [
'plone.server.auth.policies.BearerAuthPolicy',
'plone.server.auth.policies.WSTokenAuthPolicy',
'auth_extractors': [
'plone.server.auth.extractors.BearerAuthPolicy',
'plone.server.auth.extractors.WSTokenAuthPolicy',
],
'auth_user_identifiers': [
'plone.server.auth.users.RootUserIdentifier'
],
'auth_token_checker': [
'plone.server.auth.checkers.SaltedHashPasswordChecker',
'auth_token_validators': [
'plone.server.auth.validators.SaltedHashPasswordValidator',
'plone.server.auth.validators.JWTValidator'
],
'default_layers': [],
'http_methods': {},
Expand All @@ -31,7 +32,11 @@
'default_permission': '',
'available_addons': {},
'api_definition': {},
'cors': {}
'cors': {},
'jwt': {
'secret': 'foobar',
'algorithm': 'HS256'
}
}

SCHEMA_CACHE = {}
Expand Down
19 changes: 6 additions & 13 deletions src/plone.server/plone/server/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,20 @@


async def authenticate_request(request):
for policy in app_settings['auth_policies']:
for policy in app_settings['auth_extractors']:
policy = resolve_or_get(policy)
token = await policy(request).extract_token()
if token:
user = await find_user(request, token)
if user:
if await authenticate_user(request, user, token):
for validator in app_settings['auth_token_validators']:
validator = resolve_or_get(validator)
user = await validator(request).validate(token)
if user:
return user


async def find_user(request, token):
for identifier in app_settings['auth_user_identifiers']:
identifier = resolve_or_get(identifier)
user = await identifier(request).get_user()
user = await identifier(request).get_user(token)
if user:
return user


async def authenticate_user(request, user, token):
for checker in app_settings['auth_token_checker']:
checker = resolve_or_get(checker)
if await checker(request).validate(user, token):
return True
return False
34 changes: 0 additions & 34 deletions src/plone.server/plone/server/auth/checkers.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,3 @@ async def extract_token(self):
'id': userid.strip(),
'token': password.strip()
}


class JWTAuthPolicy(BasePolicy):
pass
2 changes: 1 addition & 1 deletion src/plone.server/plone/server/auth/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ class RootUserIdentifier(object):
def __init__(self, request):
self.request = request

async def get_user(self):
async def get_user(self, token):
return self.request.application.root_user
70 changes: 70 additions & 0 deletions src/plone.server/plone/server/auth/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from plone.server.utils import strings_differ
from plone.server import app_settings
from plone.server.auth import find_user
import jwt

import hashlib
import uuid


class BaseValidator(object):
def __init__(self, request):
self.request = request


def hash_password(password, salt=None):
if salt is None:
salt = uuid.uuid4().hex

if isinstance(salt, str):
salt = salt.encode('utf-8')

if isinstance(password, str):
password = password.encode('utf-8')

hashed_password = hashlib.sha512(password + salt).hexdigest()
return '{}:{}'.format(salt.decode('utf-8'), hashed_password)


class SaltedHashPasswordValidator(object):

def __init__(self, request):
self.request = request

async def validate(self, token):
user = await find_user(self.request, token)
user_pw = getattr(user, 'password', None)
if (not user_pw or
':' not in user_pw or
'token' not in token):
return False
salt = user.password.split(':')[0]
if not strings_differ(hash_password(token['token'], salt), user_pw):
return user


class JWTValidator(object):
def __init__(self, request):
self.request = request

async def validate(self, token):
if token.get('type') != 'bearer':
return False

if '.' not in token.get('token', ''):
# quick way to check if actually might be jwt
return False

try:
validated_jwt = jwt.decode(
token['token'],
app_settings['jwt']['secret'],
algorithms=[app_settings['jwt']['algorithm']])
token['id'] = validated_jwt['id']
user = await find_user(self.request, token)
if user and user.id == token['id']:
return user
except jwt.exceptions.DecodeError:
pass

return False
2 changes: 1 addition & 1 deletion src/plone.server/plone/server/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pkg_resources import iter_entry_points
from plone.server import app_settings
from plone.server.async import IAsyncUtility
from plone.server.auth.checkers import hash_password
from plone.server.auth.validators import hash_password
from plone.server.auth.participation import ROOT_USER_ID
from plone.server.auth.participation import RootUser
from plone.server.content import IStaticDirectory
Expand Down
23 changes: 23 additions & 0 deletions src/plone.server/plone/server/tests/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from datetime import datetime
from datetime import timedelta
from plone.server import app_settings
from plone.server.testing import PloneFunctionalTestCase

import jwt


class TestAuth(PloneFunctionalTestCase):

def test_jwt_auth(self):
from plone.server.auth.participation import ROOT_USER_ID
jwt_token = jwt.encode({
'exp': datetime.utcnow() + timedelta(seconds=60),
'id': ROOT_USER_ID
}, app_settings['jwt']['secret']).decode('utf-8')

resp = self.layer.requester(
'GET', '/plone/plone/@addons',
token=jwt_token
)
assert resp.status_code == 200
15 changes: 11 additions & 4 deletions src/plone.server/plone/server/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,21 @@ def iter_parents(content):
content = getattr(content, '__parent__', None)


def get_authenticated_user_id(request):
if hasattr(request, 'security') and hasattr(request.security, 'participations') \
and len(request.security.participations) > 0:
return request.security.participations[0].principal.id
def get_authenticated_user(request):
if (hasattr(request, 'security') and
hasattr(request.security, 'participations') and
len(request.security.participations) > 0):
return request.security.participations[0].principal
else:
return None


def get_authenticated_user_id(request):
user = get_authenticated_user(request)
if user:
return user.id


async def apply_cors(request):
"""Second part of the cors function to validate."""
headers = {}
Expand Down
1 change: 1 addition & 0 deletions src/plone.server/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
'zope.schema',
'zope.security',
'zope.securitypolicy',
'pyjwt'
],
extras_require={
'test': [
Expand Down

0 comments on commit b94e65f

Please sign in to comment.