diff --git a/pillar/application/modules/blender_id.py b/pillar/application/modules/blender_id.py index bdd68913..0b5bd923 100644 --- a/pillar/application/modules/blender_id.py +++ b/pillar/application/modules/blender_id.py @@ -85,7 +85,7 @@ def validate_create_user(blender_id_user_id, token, oauth_subclient_id): return abort(500) # Store the token in MongoDB. - authentication.store_token(db_id, token, token_expiry) + authentication.store_token(db_id, token, token_expiry, oauth_subclient_id) return db_user, status diff --git a/pillar/application/utils/authentication.py b/pillar/application/utils/authentication.py index ce4b6ad0..a3b979d3 100644 --- a/pillar/application/utils/authentication.py +++ b/pillar/application/utils/authentication.py @@ -42,7 +42,7 @@ def validate_token(): token = request.authorization.username oauth_subclient = request.authorization.password - db_token = find_token(token) + db_token = find_token(token, oauth_subclient) if not db_token: log.debug('Token %s not found in our local database.', token) @@ -68,13 +68,14 @@ def validate_token(): return True -def find_token(token, **extra_filters): +def find_token(token, is_subclient_token=False, **extra_filters): """Returns the token document, or None if it doesn't exist (or is expired).""" tokens_collection = app.data.driver.db['tokens'] # TODO: remove expired tokens from collection. lookup = {'token': token, + 'is_subclient_token': True if is_subclient_token else {'$in': [False, None]}, 'expire_time': {"$gt": datetime.now(tz=tz_util.utc)}} lookup.update(extra_filters) @@ -82,7 +83,7 @@ def find_token(token, **extra_filters): return db_token -def store_token(user_id, token, token_expiry): +def store_token(user_id, token, token_expiry, oauth_subclient_id): """Stores an authentication token. :returns: the token document from MongoDB @@ -91,6 +92,7 @@ def store_token(user_id, token, token_expiry): token_data = { 'user': user_id, 'token': token, + 'is_subclient_token': bool(oauth_subclient_id), 'expire_time': token_expiry, } r, _, _, status = post_internal('tokens', token_data) @@ -99,7 +101,8 @@ def store_token(user_id, token, token_expiry): log.error('Unable to store authentication token: %s', r) raise RuntimeError('Unable to store authentication token.') - return r + token_data.update(r) + return token_data def create_new_user(email, username, user_id): diff --git a/pillar/settings.py b/pillar/settings.py index b276d869..83cc315d 100644 --- a/pillar/settings.py +++ b/pillar/settings.py @@ -357,6 +357,11 @@ tokens_schema = { 'type': 'datetime', 'required': True, }, + 'is_subclient_token': { + 'type': 'boolean', + 'required': False, + 'default': False, + } } files_schema = { diff --git a/tests/common_test_class.py b/tests/common_test_class.py index 427b70d0..f53f8ae8 100644 --- a/tests/common_test_class.py +++ b/tests/common_test_class.py @@ -4,10 +4,12 @@ import json import copy import sys import logging + +import datetime import os import base64 -from bson import ObjectId +from bson import ObjectId, tz_util from eve.tests import TestMinimal import pymongo.collection from flask.testing import FlaskClient @@ -93,6 +95,28 @@ class AbstractPillarTest(TestMinimal): return found['_id'], found + def create_user(self): + with self.app.test_request_context(): + users = self.app.data.driver.db['users'] + assert isinstance(users, pymongo.collection.Collection) + + result = users.insert_one({ + '_id': ObjectId('cafef00dc379cf10c4aaceaf'), + '_updated': datetime.datetime(2016, 4, 15, 13, 15, 11, tzinfo=tz_util.utc), + '_created': datetime.datetime(2016, 4, 15, 13, 15, 11, tzinfo=tz_util.utc), + 'username': 'tester', + 'groups': [], + 'roles': ['subscriber'], + 'settings': {'email_communications': 1}, + 'auth': [{'token': '', + 'user_id': unicode(BLENDER_ID_TEST_USERID), + 'provider': 'blender-id'}], + 'full_name': u'คนรักของผัดไทย', + 'email': TEST_EMAIL_ADDRESS + }) + + return result.inserted_id + def mock_blenderid_validate_unhappy(self): """Sets up Responses to mock unhappy validation flow.""" diff --git a/tests/test_auth.py b/tests/test_auth.py index 23184aff..32691b10 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,4 +1,6 @@ +import datetime import responses +from bson import tz_util from common_test_class import AbstractPillarTest, TEST_EMAIL_USER, TEST_EMAIL_ADDRESS @@ -43,3 +45,51 @@ class AuthenticationTests(AbstractPillarTest): with self.app.test_request_context( headers={'Authorization': self.make_header('knowntoken')}): self.assertTrue(auth.validate_token()) + + @responses.activate + def test_find_token(self): + """Test finding of various tokens.""" + + from application.utils import authentication as auth + + user_id = self.create_user() + + now = datetime.datetime.now(tz_util.utc) + future = now + datetime.timedelta(days=1) + past = now - datetime.timedelta(days=1) + subclient = self.app.config['BLENDER_ID_SUBCLIENT_ID'] + + with self.app.test_request_context(): + auth.store_token(user_id, 'nonexpired-main', future, None) + auth.store_token(user_id, 'nonexpired-sub', future, subclient) + token3 = auth.store_token(user_id, 'expired-sub', past, subclient) + + with self.app.test_request_context( + headers={'Authorization': self.make_header('nonexpired-main')}): + self.assertTrue(auth.validate_token()) + + with self.app.test_request_context( + headers={'Authorization': self.make_header('nonexpired-main', subclient)}): + self.assertFalse(auth.validate_token()) + + with self.app.test_request_context( + headers={'Authorization': self.make_header('nonexpired-sub')}): + self.assertFalse(auth.validate_token()) + + with self.app.test_request_context( + headers={'Authorization': self.make_header('nonexpired-sub', subclient)}): + self.assertTrue(auth.validate_token()) + + with self.app.test_request_context( + headers={'Authorization': self.make_header('expired-sub', subclient)}): + self.assertFalse(auth.validate_token()) + + self.mock_blenderid_validate_happy() + with self.app.test_request_context( + headers={'Authorization': self.make_header('expired-sub', subclient)}): + self.assertTrue(auth.validate_token()) + + # We now should be able to find a new token for this user. + found_token = auth.find_token('expired-sub', subclient) + self.assertIsNotNone(found_token) + self.assertNotEqual(token3['_id'], found_token['_id'])