class FacadeTest(FluidinfoTestCase): """ Simple tests of L{Facade} functionality that are not to do with user creation or authentication. """ resources = [('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeTest, self).setUp() factory = FluidinfoSessionFactory('API-9000') self.transact = Transact(self.threadPool) self.facade = Facade(self.transact, factory) self.system = createSystemData() @inlineCallbacks def testCreateAnonymousSession(self): """ L{FacadeAuthMixin.createAnonymousSession} creates a L{FluidinfoSession} for the anonymous user C{anon} so that anonymous requests coming from the C{WSFE} can be correctly verified by the L{Facade}. """ anon = self.system.users[u'anon'] self.store.commit() session = yield self.facade.createAnonymousSession() self.assertEqual('anon', session.auth.username) self.assertEqual(anon.objectID, session.auth.objectID)
class RootResourceTest(FluidinfoTestCase): resources = [('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(RootResourceTest, self).setUp() factory = FluidinfoSessionFactory('API-9000') transact = Transact(self.threadPool) createSystemData() self.checker = AnonymousChecker() self.checker.facadeClient = Facade(transact, factory) getConfig().set('service', 'allow-anonymous-access', 'False') @inlineCallbacks def testAnonymousAccessDenied(self): """ L{FacadeAnonymousCheckerTest.requestAvatarId} returns an C{UnauthorizedLogin} for access by the C{anon} user if the C{allow-anonymous-access} configuration option is C{False}. The C{UnauthorizedLogin} is the C{session} attribute in L{RootResource} and must result in the C{getChild} method returning a L{WSFEUnauthorizedResource} instance. """ self.store.commit() session = yield self.checker.requestAvatarId(credentials=None) self.assertTrue(isinstance(session, UnauthorizedLogin)) root = RootResource(self.checker.facadeClient, session) request = FakeRequest() root.getChild('/', request)
class SecureRecentActivityAPIWithBrokenCacheTest(RecentActivityAPITestMixin, FluidinfoTestCase): resources = [('cache', BrokenCacheResource()), ('client', IndexResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource())] def setUp(self): super(SecureRecentActivityAPIWithBrokenCacheTest, self).setUp() createSystemData() UserAPI().create([(u'user', u'secret', u'User', u'*****@*****.**')]) self.user = getUser(u'user') self.recentActivity = SecureRecentActivityAPI(self.user) def getObjectAPI(self, user): """Get an L{CachingObjectAPI} instance for the specified user. @param user: The L{User} to configure the L{CachingObjectAPI} instance. @return: An L{CachingObjectAPI} instance. """ return CachingObjectAPI(user) def getTagValueAPI(self, user): """Get a L{TagValueAPI} instance for the specified user. @param user: The L{User} to configure the L{TagValueAPI} instance. @return: A L{TagValueAPI} instance. """ return CachingTagValueAPI(user)
class RecentActivityResourceTest(FluidinfoTestCase): resources = [('log', LoggingResource())] def testGetChildForObjects(self): """ L{RecentActivityResource.getChild} returns a L{RecentObjectsActivityResource} to handle C{recent/objects} requests. """ resource = RecentActivityResource(None, None) objectID = '751ba46f-2f27-4ec3-9271-ff032bd60240' request = FakeRequest(postpath=[objectID]) leafResource = resource.getChild('objects', request) self.assertIsInstance(leafResource, RecentObjectsActivityResource) def testGetChildForAbout(self): """ L{RecentActivityResource.getChild} returns a L{RecentAboutActivityResource} to handle C{recent/about} requests. """ resource = RecentActivityResource(None, None) about = 'about' request = FakeRequest(postpath=[about]) leafResource = resource.getChild('about', request) self.assertIsInstance(leafResource, RecentAboutActivityResource) self.assertEqual(about, leafResource.about) def testGetChildForUsers(self): """ L{RecentActivityResource.getChild} returns a L{RecentUsersActivityResource} to handle C{recent/users} requests. """ resource = RecentActivityResource(None, None) username = '******' request = FakeRequest(postpath=[username]) leafResource = resource.getChild('users', request) self.assertIsInstance(leafResource, RecentUsersActivityResource) def testGetChildForItself(self): """ L{RecentActivityResource.getChild} returns itself for requests without name. """ resource = RecentActivityResource(None, None) request = FakeRequest(postpath=[]) leafResource = resource.getChild('', request) self.assertIsInstance(leafResource, RecentActivityResource) def testGetChildForNone(self): """ L{RecentActivityResource.getChild} returns a L{NoResource} for any other requests. """ resource = RecentActivityResource(None, None) request = FakeRequest(postpath=[]) leafResource = resource.getChild('invalid', request) self.assertIsInstance(leafResource, NoResource)
class CachingUserAPIWithBrokenCacheTest(UserAPITestMixin, FluidinfoTestCase): resources = [('cache', BrokenCacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource())] def setUp(self): super(CachingUserAPIWithBrokenCacheTest, self).setUp() self.system = createSystemData() self.users = CachingUserAPI()
class CachingPermissionAPIWithBrokenCacheTest(PermissionAPITestMixin, FluidinfoTestCase): resources = [('cache', BrokenCacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource())] def setUp(self): super(CachingPermissionAPIWithBrokenCacheTest, self).setUp() self.system = createSystemData() UserAPI().create([(u'username', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'username') self.permissions = CachingPermissionAPI(self.user)
class AboutTagValueIntegrityCheckerTest(FluidinfoTestCase): resources = [('log', LoggingResource(format='%(message)s')), ('store', DatabaseResource())] def setUp(self): super(AboutTagValueIntegrityCheckerTest, self).setUp() system = createSystemData() self.aboutTag = system.tags['fluiddb/about'] self.checker = AboutTagValueIntegrityChecker() self.superuser = system.users[u'fluiddb'] def testAboutTagValueWithoutValue(self): """ L{AboutTagValueIntegrityChecker.check} logs an error if the given L{AboutTagValue} doesn't have an associated L{TagValue}. """ object1 = uuid4() object2 = uuid4() aboutTagValue1 = createAboutTagValue(object1, u'Test object 1') aboutTagValue2 = createAboutTagValue(object2, u'Test object 2') createTagValue(self.superuser.id, self.aboutTag.id, object2, u'Test object 2') self.checker.check([aboutTagValue1, aboutTagValue2]) self.assertEqual( 'Integrity Error in object %s: ' "AboutTagValue doesn't have an associated TagValue.\n" % object1, self.log.getvalue()) def testAboutTagValueWithWrongValue(self): """ L{AboutTagValueIntegrityChecker.check} logs an error if the given L{AboutTagValue} and its L{TagValue} dont match. """ object1 = uuid4() object2 = uuid4() aboutTagValue1 = createAboutTagValue(object1, u'Test object 1') createTagValue(self.superuser.id, self.aboutTag.id, object1, u'Wrong tag value') aboutTagValue2 = createAboutTagValue(object2, u'Test object 2') createTagValue(self.superuser.id, self.aboutTag.id, object2, u'Test object 2') self.checker.check([aboutTagValue1, aboutTagValue2]) self.assertEqual( 'Integrity Error in object %s: ' "AboutTagValue doesn't match its TagValue.\n" % object1, self.log.getvalue())
class ObjectCacheTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource(format='%(message)s'))] def setUp(self): super(ObjectCacheTest, self).setUp() self.objectCache = ObjectCache() def testGetReturnsObjectsInCache(self): """L{ObjectCache.get} returns the objectIDs saved in the cache.""" objectID1 = uuid4() objectID2 = uuid4() self.cache.set(u'about:about1', str(objectID1)) self.cache.set(u'about:about2', str(objectID2)) result = self.objectCache.get([u'about1', u'about2']) expected = {u'about1': objectID1, u'about2': objectID2} self.assertEqual(expected, result.results) self.assertEqual([], result.uncachedValues) def testGetReturnsUncachedValues(self): """ L{ObjectCache.get} returns values not found in the cache in the C{uncachedValues} field of the L{CacheResult} object. """ objectID1 = uuid4() self.cache.set(u'about:about1', str(objectID1)) result = self.objectCache.get([u'about1', u'about2', u'about3']) self.assertEqual({u'about1': objectID1}, result.results) self.assertEqual([u'about2', u'about3'], result.uncachedValues) def testGetWithUnicodeAboutValue(self): """ L{ObjectCache.get} correctly get objectIDs with unicode about values. """ objectID1 = uuid4() self.cache.set(u'about:\N{HIRAGANA LETTER A}', str(objectID1)) result = self.objectCache.get([u'\N{HIRAGANA LETTER A}']) self.assertEqual({u'\N{HIRAGANA LETTER A}': objectID1}, result.results) self.assertEqual([], result.uncachedValues) def testSaveStoresValuesInTheCache(self): """L{ObjectCache.save} stores a result in the cache.""" objectID1 = uuid4() objectID2 = uuid4() self.objectCache.save({u'about1': objectID1, u'about2': objectID2}) self.assertEqual(str(objectID1), self.cache.get('about:about1')) self.assertEqual(str(objectID2), self.cache.get('about:about2'))
class FacadeCheckerTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeCheckerTest, self).setUp() factory = FluidinfoSessionFactory('API-9000') transact = Transact(self.threadPool) createSystemData() self.checker = FacadeChecker() self.checker.facadeClient = Facade(transact, factory) def testRequestAvatarIdWithIncorrectPassword(self): """ L{FacadeChecker.requestAvatarId} when passed credentials with an incorrect password must raise C{UnauthorizedLogin}. """ createUser(u'user', u'pass', u'User', u'*****@*****.**') self.store.commit() credentials = UsernamePassword('user', 'bad password') deferred = self.checker.requestAvatarId(credentials) return self.assertFailure(deferred, UnauthorizedLogin) def testRequestAvatarIdWithNonExistentUser(self): """ L{FacadeChecker.requestAvatarId} when passed credentials with a non-existent user must raise C{UnauthorizedLogin}. """ credentials = UsernamePassword('user', 'pass') deferred = self.checker.requestAvatarId(credentials) return self.assertFailure(deferred, UnauthorizedLogin) @inlineCallbacks def testRequestAvatarId(self): """ L{FacadeChecker.requestAvatarId} when passed credentials creates a L{FluidinfoSession} for the authenticated user only if credentials are correct. """ user = createUser(u'user', u'pass', u'User', u'*****@*****.**') self.store.commit() credentials = UsernamePassword('user', 'pass') session = yield self.checker.requestAvatarId(credentials) self.assertEqual(user.username, session.auth.username) self.assertEqual(user.objectID, session.auth.objectID)
class SecureObjectAPIWithBrokenCacheTest(ObjectAPITestMixin, SecureObjectAPITestMixin, FluidinfoTestCase): resources = [('cache', BrokenCacheResource()), ('client', IndexResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource())] def setUp(self): super(SecureObjectAPIWithBrokenCacheTest, self).setUp() self.system = createSystemData() UserAPI().create([(u'user', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'user') self.objects = SecureObjectAPI(self.user)
class TimerPluginTest(FluidinfoTestCase): resources = [('log', LoggingResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(TimerPluginTest, self).setUp() self.transact = Transact(self.threadPool) def testTrack(self): """ L{TimerPlugin.track} returns a context manager that records the amount of time a block of code takes to run. """ session = SampleSession('id', self.transact) session.start() try: with session.timer.track('test'): pass finally: session.stop() self.assertIn('test', session.timer.events) [info] = session.timer.events['test'] self.assertEqual(3, len(info)) self.assertTrue(isinstance(info['startDate'], datetime)) self.assertTrue(isinstance(info['stopDate'], datetime)) self.assertTrue(isinstance(info['duration'], timedelta)) def testDumpsAndLoads(self): """ Data stored by a L{TimerPlugin} can be dumped to and loaded from JSON. """ session = SampleSession('id', self.transact) session.start() try: with session.timer.track('test'): pass finally: session.stop() data = session.dumps() loadedSession = SampleSession('another-id', self.transact) loadedSession.loads(data) self.assertEqual(session.timer.events, loadedSession.timer.events)
class FacadeAnonymousCheckerTest(FluidinfoTestCase): resources = [('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeAnonymousCheckerTest, self).setUp() factory = FluidinfoSessionFactory('API-9000') transact = Transact(self.threadPool) createSystemData() self.checker = AnonymousChecker() self.checker.facadeClient = Facade(transact, factory) @inlineCallbacks def testRequestAvatarIdWithAnonymousAccessDenied(self): """ L{FacadeAnonymousCheckerTest.requestAvatarId} returns C{UnauthorizedLogin} for the C{anon} user if the C{allow-anonymous-access} configuration option is C{False}. """ getConfig().set('service', 'allow-anonymous-access', 'False') self.store.commit() session = yield self.checker.requestAvatarId(credentials=None) self.assertTrue(isinstance(session, UnauthorizedLogin)) @inlineCallbacks def testRequestAvatarId(self): """ L{FacadeAnonymousCheckerTest.requestAvatarId} creates a L{FluidinfoSession} for the anonymous 'anon' user if the C{allow-anonymous-access} configuration option is C{True}. """ getConfig().set('service', 'allow-anonymous-access', 'True') self.store.commit() session = yield self.checker.requestAvatarId(credentials=None) self.assertEqual('anon', session.auth.username)
class PermissionCacheTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource(format='%(message)s'))] def setUp(self): super(PermissionCacheTest, self).setUp() self.permissionCache = PermissionCache() def setList(self, name, values): """Small helper method to put a C{list} in the cache.""" for item in values: self.cache.rpush(name, item) def testGetTagPermissions(self): """ L{PermissionCache.getTagPermissions} returns L{TagPermission}s stored in the cache. """ permissionDict = { Operation.UPDATE_TAG.id: [Policy.OPEN.id, [1, 2, 3]], Operation.DELETE_TAG.id: [Policy.CLOSED.id, [4, 5, 6]], Operation.CONTROL_TAG.id: [Policy.OPEN.id, [7, 8, 9]], Operation.WRITE_TAG_VALUE.id: [Policy.CLOSED.id, [10, 11, 12]], Operation.READ_TAG_VALUE.id: [Policy.OPEN.id, [13, 14, 15]], Operation.DELETE_TAG_VALUE.id: [Policy.CLOSED.id, [16, 17, 18]], Operation.CONTROL_TAG_VALUE.id: [Policy.OPEN.id, [19, 20, 21]] } self.cache.set('permission:tag:test/tag1', json.dumps(permissionDict)) permissionDict = { Operation.UPDATE_TAG.id: [Policy.CLOSED.id, [10, 15, 20]], Operation.DELETE_TAG.id: [Policy.OPEN.id, [25, 30, 35]], Operation.CONTROL_TAG.id: [Policy.CLOSED.id, [40, 45, 50]], Operation.WRITE_TAG_VALUE.id: [Policy.OPEN.id, [55, 60, 65]], Operation.READ_TAG_VALUE.id: [Policy.CLOSED.id, [70, 75, 80]], Operation.DELETE_TAG_VALUE.id: [Policy.OPEN.id, [85, 90, 95]], Operation.CONTROL_TAG_VALUE.id: [Policy.CLOSED.id, [100, 105, 110]] } self.cache.set('permission:tag:test/tag2', json.dumps(permissionDict)) result = self.permissionCache.getTagPermissions( [u'test/tag1', u'test/tag2']) self.assertEqual([], result.uncachedValues) permission1 = result.results[u'test/tag1'] self.assertEqual((Policy.OPEN, [1, 2, 3]), permission1.get(Operation.UPDATE_TAG)) self.assertEqual((Policy.CLOSED, [4, 5, 6]), permission1.get(Operation.DELETE_TAG)) self.assertEqual((Policy.OPEN, [7, 8, 9]), permission1.get(Operation.CONTROL_TAG)) self.assertEqual((Policy.CLOSED, [10, 11, 12]), permission1.get(Operation.WRITE_TAG_VALUE)) self.assertEqual((Policy.OPEN, [13, 14, 15]), permission1.get(Operation.READ_TAG_VALUE)) self.assertEqual((Policy.CLOSED, [16, 17, 18]), permission1.get(Operation.DELETE_TAG_VALUE)) self.assertEqual((Policy.OPEN, [19, 20, 21]), permission1.get(Operation.CONTROL_TAG_VALUE)) permission2 = result.results[u'test/tag2'] self.assertEqual((Policy.CLOSED, [10, 15, 20]), permission2.get(Operation.UPDATE_TAG)) self.assertEqual((Policy.OPEN, [25, 30, 35]), permission2.get(Operation.DELETE_TAG)) self.assertEqual((Policy.CLOSED, [40, 45, 50]), permission2.get(Operation.CONTROL_TAG)) self.assertEqual((Policy.OPEN, [55, 60, 65]), permission2.get(Operation.WRITE_TAG_VALUE)) self.assertEqual((Policy.CLOSED, [70, 75, 80]), permission2.get(Operation.READ_TAG_VALUE)) self.assertEqual((Policy.OPEN, [85, 90, 95]), permission2.get(Operation.DELETE_TAG_VALUE)) self.assertEqual((Policy.CLOSED, [100, 105, 110]), permission2.get(Operation.CONTROL_TAG_VALUE)) def testGetTagPermissionsReturnsUncachedValues(self): """ L{PermissionCache.getTagPermissions} returns the paths of L{TagPermission}s not found in the cache. """ permissionDict = { Operation.UPDATE_TAG.id: [Policy.OPEN.id, [1, 2, 3]], Operation.DELETE_TAG.id: [Policy.CLOSED.id, [4, 5, 6]], Operation.CONTROL_TAG.id: [Policy.OPEN.id, [7, 8, 9]], Operation.WRITE_TAG_VALUE.id: [Policy.CLOSED.id, [10, 11, 12]], Operation.READ_TAG_VALUE.id: [Policy.OPEN.id, [13, 14, 15]], Operation.DELETE_TAG_VALUE.id: [Policy.CLOSED.id, [16, 17, 18]], Operation.CONTROL_TAG_VALUE.id: [Policy.OPEN.id, [19, 20, 21]] } self.cache.set('permission:tag:test/tag1', json.dumps(permissionDict)) result = self.permissionCache.getTagPermissions( [u'test/tag1', u'test/tag2']) self.assertEqual([u'test/tag2'], result.uncachedValues) self.assertIn(u'test/tag1', result.results) def testGetTagPermissionsWithEmptyPaths(self): """ L{PermissionCache.getTagPermissions} returns an empty L{CacheResult} if no paths are provided. """ cached = self.permissionCache.getTagPermissions([]) self.assertEqual({}, cached.results) self.assertEqual([], cached.uncachedValues) def testGetNamespacePermissions(self): """ L{PermissionCache.getNamespacePermissions} returns L{NamespacePermission}s stored in the cache. """ permissionDict = { Operation.CREATE_NAMESPACE.id: [Policy.OPEN.id, [1, 2, 3]], Operation.UPDATE_NAMESPACE.id: [Policy.CLOSED.id, [4, 5, 6]], Operation.DELETE_NAMESPACE.id: [Policy.OPEN.id, [7, 8, 9]], Operation.LIST_NAMESPACE.id: [Policy.CLOSED.id, [10, 11, 12]], Operation.CONTROL_NAMESPACE.id: [Policy.OPEN.id, [13, 14, 15]], } self.cache.set('permission:namespace:test/namespace1', json.dumps(permissionDict)) permissionDict = { Operation.CREATE_NAMESPACE.id: [Policy.CLOSED.id, [5, 10, 15]], Operation.UPDATE_NAMESPACE.id: [Policy.OPEN.id, [20, 25, 30]], Operation.DELETE_NAMESPACE.id: [Policy.CLOSED.id, [35, 40, 45]], Operation.LIST_NAMESPACE.id: [Policy.OPEN.id, [50, 55, 60]], Operation.CONTROL_NAMESPACE.id: [Policy.CLOSED.id, [65, 70, 75]], } self.cache.set('permission:namespace:test/namespace2', json.dumps(permissionDict)) result = self.permissionCache.getNamespacePermissions( [u'test/namespace1', u'test/namespace2']) permission1 = result.results[u'test/namespace1'] self.assertEqual((Policy.OPEN, [1, 2, 3]), permission1.get(Operation.CREATE_NAMESPACE)) self.assertEqual((Policy.CLOSED, [4, 5, 6]), permission1.get(Operation.UPDATE_NAMESPACE)) self.assertEqual((Policy.OPEN, [7, 8, 9]), permission1.get(Operation.DELETE_NAMESPACE)) self.assertEqual((Policy.CLOSED, [10, 11, 12]), permission1.get(Operation.LIST_NAMESPACE)) self.assertEqual((Policy.OPEN, [13, 14, 15]), permission1.get(Operation.CONTROL_NAMESPACE)) permission2 = result.results[u'test/namespace2'] self.assertEqual((Policy.CLOSED, [5, 10, 15]), permission2.get(Operation.CREATE_NAMESPACE)) self.assertEqual((Policy.OPEN, [20, 25, 30]), permission2.get(Operation.UPDATE_NAMESPACE)) self.assertEqual((Policy.CLOSED, [35, 40, 45]), permission2.get(Operation.DELETE_NAMESPACE)) self.assertEqual((Policy.OPEN, [50, 55, 60]), permission2.get(Operation.LIST_NAMESPACE)) self.assertEqual((Policy.CLOSED, [65, 70, 75]), permission2.get(Operation.CONTROL_NAMESPACE)) def testGetNamespacePermissionsReturnsUncachedValues(self): """ L{PermissionCache.getNamespacePermissions} returns the paths of L{NamespacePermission}s not found in the cache. """ permissionDict = { Operation.CREATE_NAMESPACE.id: [Policy.OPEN.id, [1, 2, 3]], Operation.UPDATE_NAMESPACE.id: [Policy.CLOSED.id, [4, 5, 6]], Operation.DELETE_NAMESPACE.id: [Policy.OPEN.id, [7, 8, 9]], Operation.LIST_NAMESPACE.id: [Policy.CLOSED.id, [10, 11, 12]], Operation.CONTROL_NAMESPACE.id: [Policy.OPEN.id, [13, 14, 15]], } self.cache.set('permission:namespace:test/namespace1', json.dumps(permissionDict)) result = self.permissionCache.getNamespacePermissions( [u'test/namespace1', u'test/namespace2']) self.assertEqual([u'test/namespace2'], result.uncachedValues) self.assertIn(u'test/namespace1', result.results) def testGetNamespacePermissionsWithEmptyPaths(self): """ L{PermissionCache.getNamespacePermissions} returns an empty L{CacheResult} if no paths are provided. """ cached = self.permissionCache.getNamespacePermissions([]) self.assertEqual({}, cached.results) self.assertEqual([], cached.uncachedValues) def testClearTagPermissionsRemovesTagPermissions(self): """ L{PermissionCache.clearTagPermissions} removes L{TagPermission}s from the cache, leaving L{NamespacePermission} for the same paths. """ permissionDict = { Operation.UPDATE_TAG.id: [Policy.OPEN.id, [1, 2, 3]], Operation.DELETE_TAG.id: [Policy.CLOSED.id, [4, 5, 6]], Operation.CONTROL_TAG.id: [Policy.OPEN.id, [7, 8, 9]], Operation.WRITE_TAG_VALUE.id: [Policy.CLOSED.id, [10, 11, 12]], Operation.READ_TAG_VALUE.id: [Policy.OPEN.id, [13, 14, 15]], Operation.DELETE_TAG_VALUE.id: [Policy.CLOSED.id, [16, 17, 18]], Operation.CONTROL_TAG_VALUE.id: [Policy.OPEN.id, [19, 20, 21]] } self.cache.set('permission:tag:test/test', json.dumps(permissionDict)) self.permissionCache.clearTagPermissions([u'test/test']) self.assertIdentical(None, self.cache.get('permission:tag:test/test')) def testClearNamespacePermissionsRemovesNamespacePermissions(self): """ L{PermissionCache.clearNamespacePermissions} removes L{NamespacePermission}s from the cache, leaving L{TagPermission} for the same paths. """ permissionDict = { Operation.CREATE_NAMESPACE.id: [Policy.OPEN.id, [1, 2, 3]], Operation.UPDATE_NAMESPACE.id: [Policy.CLOSED.id, [4, 5, 6]], Operation.DELETE_NAMESPACE.id: [Policy.OPEN.id, [7, 8, 9]], Operation.LIST_NAMESPACE.id: [Policy.CLOSED.id, [10, 11, 12]], Operation.CONTROL_NAMESPACE.id: [Policy.OPEN.id, [13, 14, 15]], } self.cache.set('permission:namespace:test/test', json.dumps(permissionDict)) self.permissionCache.clearNamespacePermissions([u'test/test']) self.assertIdentical(None, self.cache.get('permission:namespace:test/test')) def testSaveTagPermissions(self): """ L{PermissionCache.saveTagPermissions} store L{TagPermission}s in the cache. """ permissions = { 'test/tag1': TagPermission(userID=1, tagID=1), 'test/tag2': TagPermission(userID=2, tagID=2) } self.permissionCache.saveTagPermissions(permissions) expected = { str(Operation.UPDATE_TAG.id): [False, [1]], str(Operation.DELETE_TAG.id): [False, [1]], str(Operation.CONTROL_TAG.id): [False, [1]], str(Operation.WRITE_TAG_VALUE.id): [False, [1]], str(Operation.READ_TAG_VALUE.id): [True, []], str(Operation.DELETE_TAG_VALUE.id): [False, [1]], str(Operation.CONTROL_TAG_VALUE.id): [False, [1]] } self.assertEqual( expected, json.loads(self.cache.get('permission:tag:test/tag1'))) expected = { str(Operation.UPDATE_TAG.id): [False, [2]], str(Operation.DELETE_TAG.id): [False, [2]], str(Operation.CONTROL_TAG.id): [False, [2]], str(Operation.WRITE_TAG_VALUE.id): [False, [2]], str(Operation.READ_TAG_VALUE.id): [True, []], str(Operation.DELETE_TAG_VALUE.id): [False, [2]], str(Operation.CONTROL_TAG_VALUE.id): [False, [2]] } self.assertEqual( expected, json.loads(self.cache.get('permission:tag:test/tag2'))) def testSaveNamespacePermissions(self): """ L{PermissionCache.saveNamespacePermissions} store L{NamespacePermission}s in the cache. """ permissions = { 'test/ns1': NamespacePermission(userID=1, namespaceID=1), 'test/ns2': NamespacePermission(userID=2, namespaceID=2) } self.permissionCache.saveNamespacePermissions(permissions) expected = { str(Operation.CREATE_NAMESPACE.id): [False, [1]], str(Operation.UPDATE_NAMESPACE.id): [False, [1]], str(Operation.DELETE_NAMESPACE.id): [False, [1]], str(Operation.LIST_NAMESPACE.id): [True, []], str(Operation.CONTROL_NAMESPACE.id): [False, [1]] } self.assertEqual( expected, json.loads(self.cache.get('permission:namespace:test/ns1'))) expected = { str(Operation.CREATE_NAMESPACE.id): [False, [2]], str(Operation.UPDATE_NAMESPACE.id): [False, [2]], str(Operation.DELETE_NAMESPACE.id): [False, [2]], str(Operation.LIST_NAMESPACE.id): [True, []], str(Operation.CONTROL_NAMESPACE.id): [False, [2]] } self.assertEqual( expected, json.loads(self.cache.get('permission:namespace:test/ns2')))
class FacadeOAuthCheckerTest(FluidinfoTestCase): resources = [('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeOAuthCheckerTest, self).setUp() factory = FluidinfoSessionFactory('API-9000') transact = Transact(self.threadPool) createSystemData() self.checker = FacadeOAuthChecker() self.checker.facadeClient = Facade(transact, factory) @inlineCallbacks def testRequestAvatarId(self): """ L{FacadeOAuthChecker.requestAvatarId} creates a L{FluidinfoSession} for the authenticated user only if credentials are correct. """ UserAPI().create([(u'consumer', u'secret', u'Consumer', u'*****@*****.**'), (u'user', u'secret', u'User', u'*****@*****.**')]) consumerUser = getUser(u'consumer') user = getUser(u'user') api = OAuthConsumerAPI() consumer = api.register(consumerUser) token = api.getAccessToken(consumerUser, user) self.store.commit() timestamp = 1314976811 headers = {'header1': 'foo'} arguments = 'argument1=bar' # FIXME This isn't ideal. It'd be better to use a hard-coded # signature, because then we'd know when something changed. It's hard # to do that, though, because the encrypted token generated by # fluiddb.util.minitoken is always different. -jkakar request = Request.from_request('GET', u'https://fluidinfo.com/foo', headers, {'argument1': 'bar'}) signature = SignatureMethod_HMAC_SHA1().sign(request, consumer, None) nonce = 'nonce' credentials = OAuthCredentials('fluidinfo.com', consumerUser.username, token.encrypt(), 'HMAC-SHA1', signature, timestamp, nonce, 'GET', u'https://fluidinfo.com/foo', headers, arguments) session = yield self.checker.requestAvatarId(credentials) self.assertEqual(user.username, session.auth.username) self.assertEqual(user.objectID, session.auth.objectID) def testRequestAvatarIdWithTokenMadeFromWrongSecret(self): """ L{FacadeOAuthChecker.requestAvatarId} creates a L{FluidinfoSession} for the authenticated user only if the access token was created using the consumer's secret. """ secret = ''.join(sample(ALPHABET, 16)) user = createUser(u'username', u'password', u'User', u'*****@*****.**') createOAuthConsumer(user, secret=secret) self.store.commit() timestamp = 1314976811 headers = {'header1': 'foo'} arguments = 'argument1=bar' token = dataToToken('a' * 16, {'username': user.username}) signature = 'wrong' nonce = 'nonce' credentials = OAuthCredentials('fluidinfo.com', user.username, token, 'HMAC-SHA1', signature, timestamp, nonce, 'GET', u'https://fluidinfo.com/foo', headers, arguments) deferred = self.checker.requestAvatarId(credentials) return self.assertFailure(deferred, UnauthorizedLogin) def testRequestAvatarIdInvalidToken(self): """ L{FacadeOAuthChecker.requestAvatarId} creates a L{FluidinfoSession} for the authenticated user only if the access token was properly formed (by calling dataToToken). """ secret = ''.join(sample(ALPHABET, 16)) user = createUser(u'username', u'password', u'User', u'*****@*****.**') createOAuthConsumer(user, secret=secret) self.store.commit() timestamp = 1314976811 headers = {'header1': 'foo'} arguments = 'argument1=bar' token = 'token' signature = 'wrong' nonce = 'nonce' credentials = OAuthCredentials('fluidinfo.com', user.username, token, 'HMAC-SHA1', signature, timestamp, nonce, 'GET', u'https://fluidinfo.com/foo', headers, arguments) deferred = self.checker.requestAvatarId(credentials) return self.assertFailure(deferred, UnauthorizedLogin)
class FacadeOAuth2CheckerTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeOAuth2CheckerTest, self).setUp() factory = FluidinfoSessionFactory('API-9000') transact = Transact(self.threadPool) createSystemData() self.checker = FacadeOAuth2Checker() self.checker.facadeClient = Facade(transact, factory) @inlineCallbacks def testRequestAvatarId(self): """ L{FacadeOAuth2Checker.requestAvatarId} creates a L{FluidinfoSession} for the authenticated user only if credentials are correct. """ UserAPI().create([(u'consumer', u'secret', u'Consumer', u'*****@*****.**'), (u'user', u'secret', u'User', u'*****@*****.**')]) consumerUser = getUser(u'consumer') user = getUser(u'user') api = OAuthConsumerAPI() api.register(consumerUser) token = api.getAccessToken(consumerUser, user) self.store.commit() credentials = OAuth2Credentials(u'consumer', 'secret', token.encrypt()) session = yield self.checker.requestAvatarId(credentials) self.assertEqual(user.username, session.auth.username) self.assertEqual(user.objectID, session.auth.objectID) def testRequestAvatarIdWithTokenMadeFromWrongSecret(self): """ L{FacadeOAuth2Checker.requestAvatarId} creates a L{FluidinfoSession} for the authenticated user only if the access token was created using the consumer's secret. """ user1 = createUser(u'user1', u'pass1', u'User1', u'*****@*****.**') createOAuthConsumer(user1, secret='secret16charlng1') user2 = createUser(u'user2', u'pass2', u'User2', u'*****@*****.**') self.store.commit() token = dataToToken('a' * 16, {'username': user2.username}) credentials = OAuth2Credentials(u'user1', u'pass1', token) deferred = self.checker.requestAvatarId(credentials) return self.assertFailure(deferred, UnauthorizedLogin) def testRequestAvatarIdWithInvalidToken(self): """ L{FacadeOAuth2Checker.requestAvatarId} creates a L{FluidinfoSession} for the authenticated user only if the access token was properly formed (by calling dataToToken). """ user = createUser(u'user', u'pass', u'User', u'*****@*****.**') createOAuthConsumer(user, secret='secret16charlng1') self.store.commit() credentials = OAuth2Credentials(u'user', u'pass', token='xxx') deferred = self.checker.requestAvatarId(credentials) return self.assertFailure(deferred, UnauthorizedLogin)
class FacadeTagMixinTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeTagMixinTest, self).setUp() createSystemData() self.transact = Transact(self.threadPool) factory = FluidinfoSessionFactory('API-9000') self.facade = Facade(self.transact, factory) UserAPI().create([(u'username', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'username') self.permissions = CachingPermissionAPI(self.user) @inlineCallbacks def testGetTagWithoutData(self): """ L{FacadeTagMixin.getTag} raises a L{TNonexistentTag} exception if the requested L{Tag.path} doesn't exist. """ self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.getTag(session, u'username/unknown', False) yield self.assertFailure(deferred, TNonexistentTag) @inlineCallbacks def testGetTag(self): """ L{FacadeTagMixin.getTag} returns a L{TTag} instance with information about the requested L{Tag}. """ result = TagAPI(self.user).create([(u'username/tag', u'description')]) [(objectID, path)] = result self.store.commit() with login(u'username', uuid4(), self.transact) as session: result = yield self.facade.getTag(session, u'username/tag', False) self.assertEqual(str(objectID), result.objectId) self.assertEqual(u'username/tag', result.path) self.assertTrue(result.indexed) @inlineCallbacks def testGetTagWithDescription(self): """ L{FacadeTagMixin.getTag} includes the L{Tag.description}, if it was requested. """ TagAPI(self.user).create([(u'username/tag', u'A tag.')]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: result = yield self.facade.getTag(session, u'username/tag', True) self.assertEqual(u'A tag.', result.description) @inlineCallbacks def testCreateTag(self): """L{FacadeTagMixin.createTag} creates a new L{Tag}.""" self.store.commit() with login(u'username', uuid4(), self.transact) as session: objectID = yield self.facade.createTag( session, u'username', u'tag', u'A tag.', 'ignored', 'ignored') self.assertNotIdentical(None, objectID) self.store.rollback() tag = getTags(paths=[u'username/tag']).one() self.assertIdentical(self.user, tag.creator) self.assertIdentical(self.user.namespace, tag.namespace) self.assertEqual(u'username/tag', tag.path) self.assertEqual(u'tag', tag.name) self.assertEqual(objectID, str(tag.objectID)) @inlineCallbacks def testCreateTagWithExistingPath(self): """ L{FacadeTagMixin.createTag} raises a L{TTagAlreadyExists} exception if the new L{Tag} already exists. """ TagAPI(self.user).create([(u'username/name', u'A tag.')]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.createTag( session, u'username', u'name', u'A tag.', 'ignored', 'ignored') yield self.assertFailure(deferred, TTagAlreadyExists) @inlineCallbacks def testCreateTagWithInvalidPath(self): """ L{FacadeTagMixin.createTag} raises a L{TInvalidPath} exception if the path of the L{Tag} is not well formed. """ self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.createTag(session, u'username', u'bad name', u'A tag.', 'ignored', 'ignored') yield self.assertFailure(deferred, TInvalidPath) @inlineCallbacks def testCreateTagWithUnknownParent(self): """ L{FacadeTagMixin.createTag} raises a L{TNonexistentTag} exception if a non-existent parent L{Tag} is specified. """ self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.createTag( session, u'unknown', u'name', u'A tag.', 'ignored', 'ignored') yield self.assertFailure(deferred, TNonexistentTag) @inlineCallbacks def testCreateIsDenied(self): """ L{Facade.createTag} raises a L{TPathPermissionDenied} exception if the user doesn't have C{CREATE} permissions on the parent L{Namespace}. """ self.permissions.set([(u'username', Operation.CREATE_NAMESPACE, Policy.CLOSED, [])]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.createTag( session, u'username', u'test', u'A tag.', 'ignored', 'ignored') yield self.assertFailure(deferred, TPathPermissionDenied) @inlineCallbacks def testUpdateTag(self): """ L{FacadeTagMixin.updateTag} updates the description for an existing L{Tag}. """ tags = TagAPI(self.user) tags.create([(u'username/name', u'A tag.')]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: yield self.facade.updateTag(session, u'username/name', u'A new description.') self.store.rollback() result = tags.get([u'username/name'], withDescriptions=True) self.assertEqual(u'A new description.', result[u'username/name']['description']) @inlineCallbacks def testUpdateTagWithUnknownPath(self): """ L{FacadeTagMixin.updateTag} raises a L{TNonexistentTag} exception if the requested L{Tag.path} doesn't exist. """ self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.updateTag(session, u'username/unknown', u'A new description.') yield self.assertFailure(deferred, TNonexistentTag) @inlineCallbacks def testUpdateIsDenied(self): """ L{Facade.updateTag} raises a L{TPathPermissionDenied} exception if the user doesn't have C{UPDATE} permissions on the specified L{Tag}. """ TagAPI(self.user).create([(u'username/test', u'A tag.')]) self.permissions.set([(u'username/test', Operation.UPDATE_TAG, Policy.CLOSED, [])]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.updateTag(session, u'username/test', u'A new description.') yield self.assertFailure(deferred, TPathPermissionDenied) @inlineCallbacks def testDeleteTag(self): """L{FacadeTagMixin.deleteTag} deletes a L{Tag}.""" tags = TagAPI(self.user) tags.create([(u'username/name', u'A tag.')]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: yield self.facade.deleteTag(session, u'username/name') self.store.rollback() self.assertEqual({}, tags.get([u'username/name'])) @inlineCallbacks def testDeleteTagWithUnknownPath(self): """ L{FacadeTagMixin.deleteTag} raises a L{TNonexistentTag} exception if the requested L{Tag.path} doesn't exist. """ self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.deleteTag(session, u'username/unknown') yield self.assertFailure(deferred, TNonexistentTag) @inlineCallbacks def testDeleteTagWithData(self): """ L{FacadeTagMixin.deleteTag} removes L{TagValue}s associated with the deleted L{Tag}. """ objectID = uuid4() TagAPI(self.user).create([(u'username/tag', u'A tag.')]) tagValues = TagValueAPI(self.user) tagValues.set({objectID: {u'username/tag': 42}}) self.store.commit() with login(u'username', uuid4(), self.transact) as session: yield self.facade.deleteTag(session, u'username/tag') self.store.rollback() self.assertEqual({}, tagValues.get(objectIDs=[objectID], paths=[u'username/tag'])) @inlineCallbacks def testDeleteIsDenied(self): """ L{Facade.deleteTag} raises a L{TPathPermissionDenied} exception if the user doesn't have C{DELETE} permissions on the specified L{Tag}. """ TagAPI(self.user).create([(u'username/test', u'A tag.')]) self.permissions.set([(u'username/test', Operation.DELETE_TAG, Policy.OPEN, [u'username'])]) self.store.commit() with login(u'username', uuid4(), self.transact) as session: deferred = self.facade.deleteTag(session, u'username/test') yield self.assertFailure(deferred, TPathPermissionDenied)
class OAuthEchoResourceTest(FluidinfoTestCase, FakeReactorAndConnectMixin): resources = [('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(OAuthEchoResourceTest, self).setUp() self.agent = Agent(self.FakeReactor()) self.transact = Transact(self.threadPool) system = createSystemData() self.anonymous = system.users[u'anon'] OAuthConsumerAPI().register(self.anonymous) self.agent = Agent(self.FakeReactor()) def testRenderWithMissingServiceProviderHeader(self): """ A C{BAD_REQUEST} HTTP status code is returned if the C{X-Auth-Service-Provider} header is not in the request, and the C{X-Fluiddb-*} response headers indicate that the header is missing. """ headers = {'X-Verify-Credentials-Authorization': ['OAuth ...']} request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session) self.assertEqual('', resource.render_GET(request)) headers = dict(request.responseHeaders.getAllRawHeaders()) yield resource.deferred self.assertEqual( { 'X-Fluiddb-Error-Class': ['MissingHeader'], 'X-Fluiddb-Header': ['X-Auth-Service-Provider'], 'X-Fluiddb-Request-Id': [session.id] }, headers) def testRenderWithUnknownServiceProvider(self): """ A C{BAD_REQUEST} HTTP status code is returned if the C{X-Auth-Service-Provider} in the header is not supported. """ headers = { 'X-Verify-Credentials-Authorization': ['OAuth ...'], 'X-Auth-Service-Provider': ['https://example.com/1/verify'] } request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session) self.assertEqual('', resource.render_GET(request)) headers = dict(request.responseHeaders.getAllRawHeaders()) yield resource.deferred self.assertEqual( { 'X-Fluiddb-Error-Class': ['UnknownServiceProvider'], 'X-Fluiddb-Request-Id': [session.id] }, headers) def testRenderWithMissingAuthorizationHeader(self): """ A C{BAD_REQUEST} HTTP status code is returned if the C{X-Verify-Credentials-Authorization} header is not in the request, and the C{X-Fluiddb-*} response headers indicate that the header is missing. """ headers = {'X-Auth-Service-Provider': [TWITTER_URL]} request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session) self.assertEqual('', resource.render_GET(request)) headers = dict(request.responseHeaders.getAllRawHeaders()) yield resource.deferred self.assertEqual( { 'X-Fluiddb-Error-Class': ['MissingHeader'], 'X-Fluiddb-Header': ['X-Verify-Credentials-Authorization'], 'X-Fluiddb-Request-Id': [session.id] }, headers) def testRenderWithUnsupportedAuthorizationHeader(self): """ A C{BAD_REQUEST} HTTP status code is returned if the C{X-Verify-Credentials-Authorization} in the header doesn't use the C{OAuth} scheme. """ headers = { 'X-Verify-Credentials-Authorization': ['Basic ...'], 'X-Auth-Service-Provider': [TWITTER_URL] } request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session) self.assertEqual('', resource.render_GET(request)) headers = dict(request.responseHeaders.getAllRawHeaders()) yield resource.deferred self.assertEqual( { 'X-Fluiddb-Error-Class': ['BadHeader'], 'X-Fluiddb-Header': ['X-Verify-Credentials-Authorization'], 'X-Fluiddb-Request-Id': [session.id] }, headers) @inlineCallbacks def testRenderWithSuccessfulVerification(self): """ An C{OK} HTTP status code is returned if the authorization is successfully verified by the service provider. The results of the call to the service provider to verify credentials are returned to the user. """ UserAPI().create([(u'john', 'secret', u'John', u'*****@*****.**')]) TwitterUserAPI().create(u'john', 1984245) self.store.commit() self.agent._connect = self._connect headers = { 'X-Verify-Credentials-Authorization': ['OAuth ...'], 'X-Auth-Service-Provider': [TWITTER_URL] } request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session, self.agent) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) [(_, responseDeferred)] = self.protocol.requests data = {'id': 1984245, 'screen_name': u'john', 'name': u'John'} response = FakeResponse(ResponseDone(), dumps(data)) responseDeferred.callback(response) result = yield resource.deferred self.assertTrue(result['access-token']) self.assertTrue(result['renewal-token']) del result['access-token'] del result['renewal-token'] self.assertEqual( { 'username': u'john', 'new-user': False, 'missing-password': False, 'data': data, 'uid': 1984245 }, result) self.assertEqual(OK, request.code) self.assertEqual(data, loads(request.written.getvalue())) @inlineCallbacks def testRenderWithNewUser(self): """ Missing L{User}s are created automatically and linked to L{TwitterUser}s for authorized UIDs. """ self.assertNotIn(u'john', UserAPI().get([u'john'])) self.store.commit() self.agent._connect = self._connect headers = { 'X-Verify-Credentials-Authorization': ['OAuth ...'], 'X-Auth-Service-Provider': [TWITTER_URL] } request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session, self.agent) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) [(_, responseDeferred)] = self.protocol.requests data = {'id': 1984245, 'screen_name': u'john', 'name': u'John'} response = FakeResponse(ResponseDone(), dumps(data)) responseDeferred.callback(response) result = yield resource.deferred self.assertTrue(result['access-token']) self.assertTrue(result['renewal-token']) del result['access-token'] del result['renewal-token'] self.assertEqual( { 'username': u'john', 'new-user': True, 'missing-password': True, 'data': data, 'uid': 1984245 }, result) self.assertEqual(OK, request.code) self.assertEqual(data, loads(request.written.getvalue())) headers = dict(request.responseHeaders.getAllRawHeaders()) self.assertTrue(headers['X-Fluiddb-Access-Token']) self.assertTrue(headers['X-Fluiddb-Renewal-Token']) del headers['X-Fluiddb-Access-Token'] del headers['X-Fluiddb-Renewal-Token'] self.assertEqual( { 'X-Fluiddb-New-User': ['true'], 'X-Fluiddb-Missing-Password': ['true'], 'X-Fluiddb-Username': ['am9obg=='] }, # username is in base64. headers) self.store.rollback() self.assertIn(u'john', UserAPI().get([u'john'])) @inlineCallbacks def testRenderWithUserConflict(self): """ A C{CONFLICT} HTTP status code is returned if the authorization is successfully verified by the service provider, but the username clashes with an existing L{User} that isn't linked to the Twitter UID. The offending username is returned UTF-8 and base64 encoded. """ username = u'john\N{HIRAGANA LETTER A}' UserAPI().create([(username, 'secret', u'John', u'*****@*****.**')]) self.store.commit() self.agent._connect = self._connect headers = { 'X-Verify-Credentials-Authorization': ['OAuth ...'], 'X-Auth-Service-Provider': [TWITTER_URL] } request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session, self.agent) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) [(_, responseDeferred)] = self.protocol.requests data = {'id': 1984245, 'screen_name': username, 'name': u'John'} response = FakeResponse(ResponseDone(), dumps(data)) responseDeferred.callback(response) yield resource.deferred self.assertEqual(CONFLICT, request.code) headers = dict(request.responseHeaders.getAllRawHeaders()) encodedUsername = b64encode(username.encode('utf-8')) self.assertEqual( { 'X-Fluiddb-Error-Class': ['UsernameConflict'], 'X-Fluiddb-Username': [encodedUsername], 'X-Fluiddb-Request-Id': [session.id] }, headers) @inlineCallbacks def testRenderWithFailingServiceProviderCall(self): """ A C{SERVICE_UNAVAILABLE} HTTP status code is returned if an error occurs while communicating with the L{ServiceProvider}. """ UserAPI().create([(u'user', 'secret', u'User', u'*****@*****.**')]) TwitterUserAPI().create(u'user', 1984245) self.store.commit() self.agent._connect = self._connect headers = { 'X-Verify-Credentials-Authorization': ['OAuth ...'], 'X-Auth-Service-Provider': [TWITTER_URL] } request = FakeRequest(headers=Headers(headers)) with login(u'anon', self.anonymous.objectID, self.transact) as session: resource = OAuthEchoResource(session, self.agent) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) [(_, responseDeferred)] = self.protocol.requests response = FakeResponse(RuntimeError(), None) responseDeferred.callback(response) yield resource.deferred self.assertEqual(SERVICE_UNAVAILABLE, request.code)
class BatchIndexTest(FluidinfoTestCase): resources = [('config', ConfigResource()), ('log', LoggingResource(format='%(message)s')), ('store', DatabaseResource())] def setUp(self): super(BatchIndexTest, self).setUp() # We use low-level functions here instead of model API methods because # we want to avoid an automatic update of the objects table. user = createUser(u'username', u'secret', u'User', u'*****@*****.**') namespace = createNamespace(user, u'username', None) tag = createTag(user, namespace, u'tag') self.userID = user.id self.tagID = tag.id tempPath = self.config.get('service', 'temp-path') self.objectsFilename = os.path.join(tempPath, 'objects.txt') def tearDown(self): super(BatchIndexTest, self).tearDown() if os.path.exists(self.objectsFilename): os.remove(self.objectsFilename) def createObjectsFile(self): """Helper function to create a file with a list of all object IDs.""" allObjects = set(getTagValues().values(TagValue.objectID)) with open(self.objectsFilename, 'w') as objectsFile: for objectID in allObjects: objectsFile.write(str(objectID) + '\n') def testBatchIndexTouchesAllObjects(self): """C{batchIndex} touches all objects in the given file.""" createTagValue(self.userID, self.tagID, uuid4(), 10) createTagValue(self.userID, self.tagID, uuid4(), 20) allObjects = set(getTagValues().values(TagValue.objectID)) self.createObjectsFile() batchIndex(self.objectsFilename, 0, 10) touchedObjects = set(getDirtyObjects().values(DirtyObject.objectID)) self.assertEqual(allObjects, touchedObjects) def testBatchIndexTouchesTheGivenNumberOfObjectsPerInterval(self): """ C{batchIndex} touches only the max number of objects permited on each interval acording to the interval parameter. """ createTagValue(self.userID, self.tagID, uuid4(), 10) createTagValue(self.userID, self.tagID, uuid4(), 20) createTagValue(self.userID, self.tagID, uuid4(), 30) createTagValue(self.userID, self.tagID, uuid4(), 40) createTagValue(self.userID, self.tagID, uuid4(), 50) self.createObjectsFile() self.expectedTouchedObjects = 1 def fakeSleep(seconds): self.assertEqual(10 * 60, seconds) self.assertEqual(self.expectedTouchedObjects, getDirtyObjects().count()) self.expectedTouchedObjects += 1 # index objects one every ten seconds batchIndex(self.objectsFilename, 10, 1, sleepFunction=fakeSleep) def testBatchIndexLogsErrorIfObjectIDIsNotWellFormed(self): """ If C{batchIndex} encounters a malformed objectID in the file it will continue the process after printing an error in the logs. """ createTagValue(self.userID, self.tagID, uuid4(), 10) createTagValue(self.userID, self.tagID, uuid4(), 20) allObjects = set(getTagValues().values(TagValue.objectID)) self.createObjectsFile() with open(self.objectsFilename, 'a') as objectsFilename: objectsFilename.write('wrong-id') batchIndex(self.objectsFilename, 0, 10) touchedObjects = set(getDirtyObjects().values(DirtyObject.objectID)) self.assertEqual(allObjects, touchedObjects) self.assertIn("Invalid objectID: 'wrong-id'", self.log.getvalue())
class ConcreteUserResourceTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('client', IndexResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(ConcreteUserResourceTest, self).setUp() createSystemData() UserAPI().create([(u'username', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'username') factory = FluidinfoSessionFactory('API-9000') self.transact = Transact(self.threadPool) self.facade = Facade(self.transact, factory) self.store.commit() @defer.inlineCallbacks def testGET(self): """ A GET request on /users/<username> returns the complete details about the user. """ request = FakeRequest() with login(u'username', self.user.objectID, self.transact) as session: resource = ConcreteUserResource(self.facade, session, 'username') body = yield resource.deferred_render_GET(request) body = loads(body) expected = {"role": "USER", "name": "User", "id": str(self.user.objectID)} self.assertEqual(expected, body) self.assertEqual(http.OK, request.code) @defer.inlineCallbacks def testPUT(self): """ A PUT request on C{/users/<username>} updates the data for the user. """ body = dumps({'name': 'New name', 'email': '*****@*****.**', 'role': 'USER_MANAGER'}) headers = Headers({'content-length': [len(body)], 'content-type': ['application/json']}) request = FakeRequest(body=body, headers=headers) with login(u'username', self.user.objectID, self.transact) as session: resource = ConcreteUserResource(self.facade, session, 'username') yield resource.deferred_render_PUT(request) self.assertEqual(http.NO_CONTENT, request.code) body = yield resource.deferred_render_GET(FakeRequest()) body = loads(body) expected = {'role': 'USER_MANAGER', 'name': 'New name', 'id': str(self.user.objectID)} self.assertEqual(expected, body) @defer.inlineCallbacks def testPUTToUpdateRoleOnly(self): """ A PUT request on C{/users/<username>} can update only the role for the user even if other arguments are not given. """ body = dumps({'role': 'USER_MANAGER'}) headers = Headers({'content-length': [len(body)], 'content-type': ['application/json']}) request = FakeRequest(body=body, headers=headers) with login(u'username', self.user.objectID, self.transact) as session: resource = ConcreteUserResource(self.facade, session, 'username') yield resource.deferred_render_PUT(request) self.assertEqual(http.NO_CONTENT, request.code) body = yield resource.deferred_render_GET(FakeRequest()) body = loads(body) expected = {'role': 'USER_MANAGER', 'name': 'User', 'id': str(self.user.objectID)} self.assertEqual(expected, body)
class TransactTest(FluidinfoTestCase): resources = [('log', LoggingResource(format='%(message)s'))] @inlineCallbacks def testRun(self): """ L{Transact.run} executes a function in a thread, commits the transaction and returns a C{Deferred} that fires with the function's result. """ class FakeTransactionManager(object): def commit(self): self.committed = True def function(a, b=None): return a + b manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager) result = yield transact.run(function, 1, b=2) self.assertEqual(3, result) self.assertTrue(manager.committed) @inlineCallbacks def testRetries(self): """ L{Transact.run} retries the transaction if a DB error occours during a transaction collision. """ class FakeTransactionManager(object): def commit(self): self.committed = True def abort(self): self.aborted = True self.flag = False def function(): if not self.flag: self.flag = True raise psycopg2.Error('Error') return 'success' manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager, lambda seconds: None) result = yield transact.run(function) self.assertEqual('success', result) self.assertTrue(manager.aborted) self.assertTrue(manager.committed) @inlineCallbacks def testRetryWriteWarningInLog(self): """ L{Transact.run} writes a warning in the logs if a retry is done. """ class FakeTransactionManager(object): def commit(self): self.committed = True def abort(self): self.aborted = True self.flag = False def function(): if not self.flag: self.flag = True raise psycopg2.Error('Error') return 'success' manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager, lambda seconds: None) result = yield transact.run(function) self.assertEqual('success', result) self.assertTrue(manager.aborted) self.assertTrue(manager.committed) self.assertIn('Retrying a transaction', self.log.getvalue()) @inlineCallbacks def testRetriesDisconnectionErrors(self): """ L{Transact.run} retries the transaction if a L{DisconnectionError} occurs during a transaction. """ class FakeTransactionManager(object): def commit(self): self.committed = True def abort(self): self.aborted = True self.flag = False def function(): if not self.flag: self.flag = True raise DisconnectionError('Disconnected') return 'success' manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager) result = yield transact.run(function) self.assertEqual('success', result) self.assertTrue(manager.aborted) self.assertTrue(manager.committed) @inlineCallbacks def testRunWithFunctionFailure(self): """ If the given function raises an error, then L{Transact.run} aborts the transaction and re-raises the same error. """ class FakeTransactionManager(object): def abort(self): self.aborted = True def function(): raise RuntimeError('Function call exploded!') manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager) yield self.assertFailure(transact.run(function), RuntimeError) self.assertTrue(manager.aborted) @inlineCallbacks def testRunWithCommitFailure(self): """ If the specified function succeeds but the transaction fails to commit, then L{Transact.run} aborts the transaction and re-raises the commit exception. """ class FakeTransactionManager(object): def abort(self): self.aborted = True def commit(self): raise RuntimeError('Commit exploded!') def function(): pass manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager) yield self.assertFailure(transact.run(function), RuntimeError) self.assertTrue(manager.aborted) @inlineCallbacks def testReturnStormObject(self): """ A C{RuntimeError} is raised if a Storm object is returned from the transaction. Storm objects may only be used in the thread they were created in, so this provides some safety checking to prevent strange behaviour. """ class FakeTransactionManager(object): def abort(self): self.aborted = True class StormObject(object): __storm_table__ = 'storm_object' def function(): return StormObject() manager = FakeTransactionManager() transact = Transact(FakeThreadPool(), manager) yield self.assertFailure(transact.run(function), RuntimeError) self.assertTrue(manager.aborted) def testWBDefaultTransactionManager(self): """ By default L{Transact} uses the C{transaction} package as the default transaction manager. """ transact = Transact(FakeThreadPool()) self.assertIdentical(transaction, transact._transaction)
class CommentImporterTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource(format='%(message)s')), ('store', DatabaseResource())] def setUp(self): super(CommentImporterTest, self).setUp() self.system = createSystemData() UserAPI().create([(u'fluidinfo.com', u'secret', u'User', u'*****@*****.**')]) user = getUser(u'fluidinfo.com') self.objects = SecureObjectAPI(user) self.values = SecureTagValueAPI(user) def testUpload(self): """ Comments are uploaded directly to Fluidinfo. After performing an upload, all the inserted values must be present in Fluidinfo. """ when = datetime.utcnow() floatTime = timegm(when.utctimetuple()) + float(when.strftime('0.%f')) isoTime = when.isoformat() client = CommentImporter(100) client.upload([{ 'about': [u'one', u'two'], 'importer': u'fluidinfo.com', 'text': u'Here is my #wonderful comment', 'timestamp': when, 'url': u'http://twitter.com/status/9373973', 'username': u'joe' }]) about = u'fluidinfo.com joe %s' % isoTime result = self.objects.get([about]) objectID = result[about] result = self.values.get([objectID], [ u'fluidinfo.com/info/about', u'fluidinfo.com/info/text', u'fluidinfo.com/info/timestamp', u'fluidinfo.com/info/url', u'fluidinfo.com/info/username', ]) comment = result[objectID] self.assertEqual([u'one', u'two', u'#wonderful'], comment[u'fluidinfo.com/info/about'].value) self.assertEqual(u'Here is my #wonderful comment', comment[u'fluidinfo.com/info/text'].value) self.assertEqual(floatTime, comment[u'fluidinfo.com/info/timestamp'].value) self.assertEqual(u'http://twitter.com/status/9373973', comment[u'fluidinfo.com/info/url'].value) self.assertEqual(u'joe', comment[u'fluidinfo.com/info/username'].value) def testUploadWithoutAboutValues(self): """ When no explicit about values are in the uploaded comment, there must be no about values stored in Fluidinfo. """ when = datetime.utcnow() isoTime = when.isoformat() client = CommentImporter(100) client.upload([{ 'importer': u'fluidinfo.com', 'text': u'Here is my comment', 'timestamp': when, 'url': u'http://twitter.com/status/9373973', 'username': u'joe' }]) about = u'fluidinfo.com joe %s' % isoTime result = self.objects.get([about]) objectID = result[about] result = self.values.get([objectID], [u'fluidinfo.com/info/about']) comment = result[objectID] self.assertEqual([], comment[u'fluidinfo.com/info/about'].value) def testUploadMultipleComments(self): """Multiple comments are inserted in batches.""" when = datetime.utcnow() isoTime = when.isoformat() client = CommentImporter(100) client.upload([{ 'importer': u'fluidinfo.com', 'text': u'Here is my #wonderful comment', 'timestamp': when, 'url': u'http://twitter.com/status/9373973', 'username': u'joe' }, { 'importer': u'fluidinfo.com', 'text': u'A #crazy comment', 'timestamp': when, 'url': u'http://twitter.com/status/9279479379', 'username': u'mike' }]) about1 = u'fluidinfo.com joe %s' % isoTime about2 = u'fluidinfo.com mike %s' % isoTime result = self.objects.get([about1, about2]) objectID1 = result[about1] objectID2 = result[about2] comments = self.values.get([objectID1, objectID2], [u'fluidinfo.com/info/text']) comment1 = comments[objectID1] self.assertEqual(u'Here is my #wonderful comment', comment1[u'fluidinfo.com/info/text'].value) comment2 = comments[objectID2] self.assertEqual(u'A #crazy comment', comment2[u'fluidinfo.com/info/text'].value) self.assertTrue(self.log.getvalue().startswith( 'Importing 2 new comments.\nImported 2/2 new comments.\n' 'Imported 2 comments in ')) def testUploadUsesBatchSize(self): """ Comments are uploaded in batches when possible, depending on the batch size. """ when = datetime.utcnow() client = CommentImporter(1) client.upload([{ 'importer': u'fluidinfo.com', 'text': u'Here is my #wonderful comment', 'timestamp': when, 'url': u'http://twitter.com/status/9373973', 'username': u'joe' }, { 'importer': u'fluidinfo.com', 'text': u'A #crazy comment', 'timestamp': when, 'url': u'http://twitter.com/status/9279479379', 'username': u'mike' }]) self.assertTrue(self.log.getvalue().startswith( 'Importing 2 new comments.\nImported 1/2 new comments.\n' 'Imported 2/2 new comments.\nImported 2 comments in ')) def testUploadLogsMessage(self): """ Uploads must prefix log output with the passed message. """ when = datetime.utcnow() client = CommentImporter(100) client.upload([{ 'importer': u'fluidinfo.com', 'text': u'Here is my #wonderful comment', 'timestamp': when, 'url': u'http://twitter.com/status/9373973', 'username': u'joe' }], 'message-xxx') self.assertTrue(self.log.getvalue().startswith( 'message-xxx: Importing 1 new comments.\n' 'message-xxx: Imported 1/1 new comments.\n' 'message-xxx: Imported 1 comments in '))
class DelegatorTest(FluidinfoTestCase, FakeReactorAndConnectMixin): resources = [('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(DelegatorTest, self).setUp() self.agent = Agent(self.FakeReactor()) self.transact = Transact(self.threadPool) createSystemData() @inlineCallbacks def testGetUser(self): """ L{Delegator.getUser} returns a C{(User, data)} 2-tuple when the service provider successfully verifies credentials and a mapping between a Fluidinfo L{User} and the L{TwitterUser} being verified exists. """ UserAPI().create([ (u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'user', 'secret', u'User', u'*****@*****.**')]) TwitterUserAPI().create(u'user', 1984245) consumer = getUser(u'consumer') OAuthConsumerAPI().register(consumer) self.store.commit() self.agent._connect = self._connect authentication = 'OAuth oauth_consumer_key="...", ...' provider = ServiceProvider(self.agent, 'https://example.com/verify') delegator = Delegator(self.transact) deferred = delegator.getUser(u'consumer', provider, authentication) [(request, responseDeferred)] = self.protocol.requests response = FakeResponse(ResponseDone(), dumps({'id': 1984245})) responseDeferred.callback(response) result = yield deferred self.assertTrue(result['access-token']) self.assertTrue(result['renewal-token']) del result['access-token'] del result['renewal-token'] self.assertEqual({'username': u'user', 'new-user': False, 'missing-password': False, 'uid': 1984245, 'data': {u'id': 1984245}}, result) @inlineCallbacks def testGetUserWithNoPassword(self): """ If a L{User} returned by L{Delegator.getUser} doesn't have a password, a C{missing-password} value is added to the result. """ UserAPI().create([ (u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'user', None, u'User', u'*****@*****.**')]) TwitterUserAPI().create(u'user', 1984245) consumer = getUser(u'consumer') OAuthConsumerAPI().register(consumer) self.store.commit() self.agent._connect = self._connect authentication = 'OAuth oauth_consumer_key="...", ...' provider = ServiceProvider(self.agent, 'https://example.com/verify') delegator = Delegator(self.transact) deferred = delegator.getUser(u'consumer', provider, authentication) [(request, responseDeferred)] = self.protocol.requests response = FakeResponse(ResponseDone(), dumps({'id': 1984245})) responseDeferred.callback(response) result = yield deferred self.assertTrue(result['access-token']) self.assertTrue(result['renewal-token']) del result['access-token'] del result['renewal-token'] self.assertEqual({'username': u'user', 'new-user': False, 'missing-password': True, 'uid': 1984245, 'data': {u'id': 1984245}}, result) @inlineCallbacks def testGetUserWithNewUser(self): """ A new L{User} is created if L{Delegator.getUser} verifies the L{TwitterUser} but can't find a user matching the Twitter screen name. A C{new-user} value is returned to indicate this situation. """ UserAPI().create([ (u'consumer', 'secret', u'Consumer', u'*****@*****.**')]) consumer = getUser(u'consumer') OAuthConsumerAPI().register(consumer) self.store.commit() self.agent._connect = self._connect authentication = 'OAuth oauth_consumer_key="...", ...' provider = ServiceProvider(self.agent, 'https://example.com/verify') delegator = Delegator(self.transact) deferred = delegator.getUser(u'consumer', provider, authentication) [(request, responseDeferred)] = self.protocol.requests response = FakeResponse(ResponseDone(), dumps({'id': 1984245, 'screen_name': u'john', 'name': u'John Doe'})) responseDeferred.callback(response) result = yield deferred self.assertTrue(result['new-user']) user = TwitterUserAPI().get(1984245) self.assertEqual(u'john', user.username) self.assertEqual(u'John Doe', user.fullname) @inlineCallbacks def testGetUserWithNewUserAndMixedCaseTwitterScreenName(self): """ A new L{User} is created if L{Delegator.getUser} verifies the L{TwitterUser} but can't find a user matching the Twitter screen name. The Twitter user's screen name should be lowercased to make the new Fluidinfo username. """ UserAPI().create([ (u'consumer', 'secret', u'Consumer', u'*****@*****.**')]) consumer = getUser(u'consumer') OAuthConsumerAPI().register(consumer) self.store.commit() self.agent._connect = self._connect authentication = 'OAuth oauth_consumer_key="...", ...' provider = ServiceProvider(self.agent, 'https://example.com/verify') delegator = Delegator(self.transact) deferred = delegator.getUser(u'consumer', provider, authentication) [(request, responseDeferred)] = self.protocol.requests response = FakeResponse(ResponseDone(), dumps({'id': 1984245, 'screen_name': u'MixedCaseName', 'name': u'John Doe'})) responseDeferred.callback(response) result = yield deferred self.assertTrue(result['new-user']) user = TwitterUserAPI().get(1984245) self.assertEqual(u'mixedcasename', user.username) @inlineCallbacks def testGetUserWithUserConflict(self): """ A L{DuplicateUserError} exception is raised if a L{User} with the same username as the Twitter user's screen name already exists, but is not associated with the Twitter UID. """ UserAPI().create([ (u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'john', 'secret', u'John', u'*****@*****.**')]) self.store.commit() self.agent._connect = self._connect authentication = 'OAuth oauth_consumer_key="...", ...' consumer = getUser(u'consumer') provider = ServiceProvider(self.agent, 'https://example.com/verify') delegator = Delegator(self.transact) deferred = delegator.getUser(consumer, provider, authentication) [(request, responseDeferred)] = self.protocol.requests response = FakeResponse(ResponseDone(), dumps({'id': 1984245, 'screen_name': u'john', 'name': u'John Doe'})) responseDeferred.callback(response) error = yield self.assertFailure(deferred, DuplicateUserError) self.assertEqual([u'john'], list(error.usernames))
class RecentUserActivityResourceTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(RecentUserActivityResourceTest, self).setUp() createSystemData() UserAPI().create([(u'username', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'username') factory = FluidinfoSessionFactory('API-9000') self.transact = Transact(self.threadPool) self.facade = Facade(self.transact, factory) @inlineCallbacks def testRenderRecentUserActivity(self): """ L{RecentUserActivityResource.deferred_render_GET} renders a response with recent activity data for the given user. """ objectID = ObjectAPI(self.user).create(u'object1') TagValueAPI(self.user).set({objectID: {u'username/tag1': u'A'}}) self.store.commit() TagValueAPI(self.user).set({objectID: {u'username/tag2': u'B'}}) self.store.commit() request = FakeRequest() with login(u'username', self.user.objectID, self.transact) as session: resource = RecentUserActivityResource(self.facade, session, u'username') body = yield resource.deferred_render_GET(request) body = json.loads(body) expected = [{ u'username': u'username', u'about': u'object1', u'id': str(objectID), u'tag': u'username/tag2', u'value': u'B' }, { u'username': u'username', u'about': u'object1', u'id': str(objectID), u'tag': u'username/tag1', u'value': u'A' }] # Clean up timestamps. for item in body: del item['updated-at'] self.assertEqual(expected, body) self.assertEqual(http.OK, request.code) @inlineCallbacks def testRenderRecentAboutActivityWithUnknownAbout(self): """ L{RecentUserActivityResource.deferred_render_GET} raises L{TNoSuchUser} if the given user doesn't exist. """ self.store.commit() request = FakeRequest() with login(u'username', self.user.objectID, self.transact) as session: resource = RecentUserActivityResource(self.facade, session, u'unknown') deferred = resource.deferred_render_GET(request) yield self.assertFailure(deferred, TNoSuchUser)
class RecentObjectsActivityResourceTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('client', IndexResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(RecentObjectsActivityResourceTest, self).setUp() createSystemData() UserAPI().create([(u'username', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'username') factory = FluidinfoSessionFactory('API-9000') self.transact = Transact(self.threadPool) self.facade = Facade(self.transact, factory) @inlineCallbacks def testRenderRecentObjectsActivity(self): """ L{RecentObjectsActivityResource.deferred_render_GET} renders a response with recent activity data for the objects returned by the given query. """ objectID = ObjectAPI(self.user).create(u'object1') self.store.commit() TagValueAPI(self.user).set({objectID: {u'username/tag1': u'A'}}) runDataImportHandler(self.client.url) request = FakeRequest(args={'query': ['has username/tag1']}) with login(u'username', self.user.objectID, self.transact) as session: resource = RecentObjectsActivityResource(self.facade, session) body = yield resource.deferred_render_GET(request) body = json.loads(body) expected = [{ u'username': u'username', u'about': u'object1', u'id': str(objectID), u'tag': u'username/tag1', u'value': u'A' }, { u'username': u'fluiddb', u'about': u'object1', u'id': str(objectID), u'tag': u'fluiddb/about', u'value': u'object1' }] # Clean up timestamps. for item in body: del item['updated-at'] self.assertEqual(expected, body) self.assertEqual(http.OK, request.code) @inlineCallbacks def testRenderRecentObjectsActivityWithNoQuery(self): """ L{RecentObjectsActivityResource.deferred_render_GET} raises L{MissingArgument} if the C{query} argument is not given. """ objectID = ObjectAPI(self.user).create(u'object1') self.store.commit() TagValueAPI(self.user).set({objectID: {u'username/tag1': u'A'}}) runDataImportHandler(self.client.url) request = FakeRequest() with login(u'username', self.user.objectID, self.transact) as session: resource = RecentObjectsActivityResource(self.facade, session) deferred = resource.deferred_render_GET(request) yield self.assertFailure(deferred, MissingArgument) def testGetChildForObjects(self): """ L{RecentObjectsActivityResource.getChild} returns a L{RecentObjectActivityResource} to handle C{recent/objects/id} requests. """ resource = RecentObjectsActivityResource(None, None) objectID = '751ba46f-2f27-4ec3-9271-ff032bd60240' request = FakeRequest(postpath=[objectID]) leafResource = resource.getChild('objects', request) self.assertIsInstance(leafResource, RecentObjectActivityResource) def testGetChildForItself(self): """ L{RecentObjetsActivityResource.getChild} returns itself for requests without name. """ resource = RecentObjectsActivityResource(None, None) request = FakeRequest(postpath=[]) leafResource = resource.getChild('', request) self.assertIsInstance(leafResource, RecentObjectsActivityResource)
class BaseCacheTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource(format='%(message)s'))] def testGetValues(self): """L{BaseCache.getValues} returns values stored in the cache.""" self.cache.set('identifier1', 'test1') self.cache.set('identifier2', 'test2') result = BaseCache().getValues([u'identifier1', u'identifier2']) self.assertEqual([u'test1', u'test2'], result) def testGetValuesWithPrefix(self): """L{BaseCache.getValues} uses the given prefix as key.""" self.cache.set('prefix:identifier1', 'test1') self.cache.set('prefix:identifier2', 'test2') cache = BaseCache() cache.keyPrefix = 'prefix:' result = cache.getValues([u'identifier1', u'identifier2']) self.assertEqual([u'test1', u'test2'], result) def testGetValuesWithUnknownValue(self): """L{BaseCache.getValues} returns L{None} for values not found.""" self.cache.set('identifier1', 'test1') result = BaseCache().getValues([u'identifier1', u'identifier2']) self.assertEqual([u'test1', None], result) def testGetValuesWithEmptyIdentifiers(self): """ L{BaseCache.getValues} returns an empty list if the list of identifiers is empty as well. """ self.assertEqual([], BaseCache().getValues([])) def testGetValuesWithError(self): """ Redis errors are ignored by L{BaseCache.getValues} and a line is written in the logs. """ cache = BaseCache() cache._client.connection_pool = ConnectionPool(port=0) result = cache.getValues([u'identifier1']) self.assertIdentical(None, result) self.assertEqual( 'Redis error: Error 111 connecting localhost:0. ' 'Connection refused.\n', self.log.getvalue()) def testSetValues(self): """L{BaseCache.setValues} sets values in the cache.""" BaseCache().setValues({'identifier1': 'test1', 'identifier2': 'test2'}) self.assertEqual('test1', self.cache.get('identifier1')) self.assertEqual('test2', self.cache.get('identifier2')) def testSetValuesWithTimeout(self): """L{BaseCache.setValues} set the expire timout of the values.""" BaseCache().setValues({'identifier1': 'test1', 'identifier2': 'test2'}) expectedTimeout = self.config.getint('cache', 'expire-timeout') self.assertAlmostEqual(expectedTimeout, self.cache.ttl('identifier1')) self.assertAlmostEqual(expectedTimeout, self.cache.ttl('identifier2')) def testSetValuesWithPrefix(self): """L{BaseCache.setValues} uses the given prefix as key.""" cache = BaseCache() cache.keyPrefix = 'prefix:' cache.setValues({'identifier1': 'test1', 'identifier2': 'test2'}) self.assertEqual('test1', self.cache.get('prefix:identifier1')) self.assertEqual('test2', self.cache.get('prefix:identifier2')) def testSetValuesWithError(self): """ Redis errors are ignored by L{BaseCache.setValues} and a line is written in the logs. """ cache = BaseCache() cache._client.connection_pool = ConnectionPool(port=0) cache.setValues({'identifier': 'test'}) self.assertEqual( 'Redis error: Error 111 connecting localhost:0. ' 'Connection refused.\n', self.log.getvalue()) def testDeleteValues(self): """L{BaseCache.deleteValues} deletes values from the cache.""" self.cache.set('identifier1', 'test1') self.cache.set('identifier2', 'test2') BaseCache().deleteValues([u'identifier1', u'identifier2']) self.assertEqual([None, None], self.cache.mget([u'identifier1', u'identifier2'])) def testDeleteValuesWithPrefix(self): """L{BaseCache.deleteValues} uses the given prefix as key.""" self.cache.set('prefix:identifier1', 'test1') self.cache.set('prefix:identifier2', 'test2') cache = BaseCache() cache.keyPrefix = 'prefix:' cache.deleteValues([u'identifier1', u'identifier2']) self.assertEqual([None, None], self.cache.mget( [u'prefix:identifier1', u'prefix:identifier2'])) def testDeleteValuesWithEmpyIdentifiers(self): """ L{BaseCache.deleteValues} doesn't show errors if an empty list of identifiers is given. """ BaseCache().deleteValues([]) self.assertEqual('', self.log.getvalue()) def testDeleteValuesWithError(self): """ Redis errors are ignored by L{BaseCache.deleteValues} and a line is written in the logs. """ cache = BaseCache() cache._client.connection_pool = ConnectionPool(port=0) cache.deleteValues(['identifier']) self.assertEqual( 'Redis error: Error 111 connecting localhost:0. ' 'Connection refused.\n', self.log.getvalue())
class RenewOAuthTokenResourceTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(RenewOAuthTokenResourceTest, self).setUp() self.transact = Transact(self.threadPool) createSystemData() @inlineCallbacks def testRenderWithSuccessfulVerification(self): """ An C{OK} HTTP status code is returned, along with new access and renewal tokens, if a renewal request is successful. """ UserAPI().create( [(u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'user', 'secret', u'User', u'*****@*****.**')]) consumer = getUser(u'consumer') user = getUser(u'consumer') api = OAuthConsumerAPI() api.register(consumer) token = api.getRenewalToken(consumer, user) self.store.commit() headers = {'X-FluidDB-Renewal-Token': [token.encrypt()]} request = FakeRequest(headers=Headers(headers)) with login(u'consumer', consumer.objectID, self.transact) as session: resource = RenewOAuthTokenResource(session) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) yield resource.deferred headers = dict(request.responseHeaders.getAllRawHeaders()) self.assertTrue(headers['X-Fluiddb-Access-Token']) self.assertTrue(headers['X-Fluiddb-Renewal-Token']) self.assertEqual(OK, request.code) @inlineCallbacks def testRenderWithUnknownConsumer(self): """ A C{BAD_REQUEST} HTTP status code is returned if an L{OAuthRenewalToken} for an unknown L{OAuthConsumer} is used in a renewal request. """ UserAPI().create( [(u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'user', 'secret', u'User', u'*****@*****.**')]) consumer = getUser(u'consumer') user = getUser(u'consumer') api = OAuthConsumerAPI() api.register(consumer) token = api.getRenewalToken(consumer, user) headers = {'X-FluidDB-Renewal-Token': [token.encrypt()]} self.store.find(OAuthConsumer).remove() self.store.commit() request = FakeRequest(headers=Headers(headers)) with login(u'consumer', consumer.objectID, self.transact) as session: resource = RenewOAuthTokenResource(session) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) yield resource.deferred headers = dict(request.responseHeaders.getAllRawHeaders()) self.assertEqual( {'X-Fluiddb-Error-Class': ['UnknownConsumer'], 'X-Fluiddb-Username': ['consumer'], 'X-Fluiddb-Request-Id': [session.id]}, headers) self.assertEqual(BAD_REQUEST, request.code) @inlineCallbacks def testRenderWithExpiredRenewalToken(self): """ An C{UNAUTHORIZED} HTTP status code is returned if an expired L{OAuthRenewalToken} is used in a renewal request. """ UserAPI().create( [(u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'user', 'secret', u'User', u'*****@*****.**')]) consumer = getUser(u'consumer') user = getUser(u'consumer') api = OAuthConsumerAPI() api.register(consumer) creationTime = datetime.utcnow() - timedelta(hours=200) token = api.getRenewalToken(consumer, user, now=lambda: creationTime) self.store.commit() headers = {'X-FluidDB-Renewal-Token': [token.encrypt()]} request = FakeRequest(headers=Headers(headers)) with login(u'consumer', consumer.objectID, self.transact) as session: resource = RenewOAuthTokenResource(session) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) yield resource.deferred headers = dict(request.responseHeaders.getAllRawHeaders()) self.assertEqual( {'X-Fluiddb-Error-Class': ['ExpiredOAuth2RenewalToken'], 'X-Fluiddb-Username': ['consumer'], 'X-Fluiddb-Request-Id': [session.id]}, headers) self.assertEqual(UNAUTHORIZED, request.code) @inlineCallbacks def testRenderWithInvalidRenewalToken(self): """ A C{BAD_REQUEST} HTTP status code is returned if an invalid L{OAuthRenewalToken} is used in a renewal request. """ UserAPI().create( [(u'consumer', 'secret', u'Consumer', u'*****@*****.**'), (u'user', 'secret', u'User', u'*****@*****.**')]) consumer = getUser(u'consumer') api = OAuthConsumerAPI() api.register(consumer) self.store.commit() headers = {'X-FluidDB-Renewal-Token': ['invalid']} request = FakeRequest(headers=Headers(headers)) with login(u'consumer', consumer.objectID, self.transact) as session: resource = RenewOAuthTokenResource(session) self.assertEqual(NOT_DONE_YET, resource.render_GET(request)) yield resource.deferred headers = dict(request.responseHeaders.getAllRawHeaders()) self.assertEqual( {'X-Fluiddb-Error-Class': ['InvalidOAuth2RenewalToken'], 'X-Fluiddb-Username': ['consumer'], 'X-Fluiddb-Request-Id': [session.id]}, headers) self.assertEqual(BAD_REQUEST, request.code)
class DatasetImporterTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource(format='%(message)s')), ('store', DatabaseResource())] def setUp(self): super(DatasetImporterTest, self).setUp() self.system = createSystemData() UserAPI().create([(u'user', u'secret', u'User', u'*****@*****.**')]) user = getUser(u'user') self.objects = SecureObjectAPI(user) self.values = SecureTagValueAPI(user) def testUpload(self): """ Object data is converted into a format compatible with the L{TagValueAPI.set} method before being uploaded directly into Fluidinfo. """ client = DatasetImporter(100) client.upload(u'user', [{ 'about': u'hello world', 'values': { u'user/bar': 13 } }]) result = self.objects.get([u'hello world']) objectID = result[u'hello world'] result = self.values.get([objectID], [u'user/bar']) value = result[objectID][u'user/bar'] self.assertEqual(13, value.value) def testUploadMultipleObjects(self): """Multiple objects are inserted in batches.""" client = DatasetImporter(100) client.upload(u'user', [{ 'about': u'hello world', 'values': { u'user/bar': 13 } }, { 'about': u'wubble', 'values': { u'user/quux': 42 } }]) aboutValues = self.objects.get([u'hello world', u'wubble']) objectID = aboutValues[u'hello world'] result = self.values.get([objectID], [u'user/bar']) value = result[objectID][u'user/bar'] self.assertEqual(13, value.value) objectID = aboutValues[u'wubble'] result = self.values.get([objectID], [u'user/quux']) value = result[objectID][u'user/quux'] self.assertEqual(42, value.value) self.assertTrue(self.log.getvalue().startswith( 'Importing 2 new objects.\nImported 2/2 new objects.\n' 'Imported 2 objects in ')) def testUploadUsesBatchSize(self): """ Objects are uploaded in batches when possible, depending on the batch size. """ client = DatasetImporter(1) client.upload(u'user', [{ 'about': u'hello world', 'values': { u'user/bar': 13 } }, { 'about': u'wubble', 'values': { u'user/quux': 42 } }]) self.assertTrue(self.log.getvalue().startswith( 'Importing 2 new objects.\nImported 1/2 new objects.\n' 'Imported 2/2 new objects.\nImported 2 objects in ')) def testUploadWithUncreatablePath(self): """ L{DatasetImporter.upload} checks permissions when importing data. An L{UnknownPathError} is raised if a specified tag doesn't exist and the L{User} doesn't have permissions to create it. """ client = DatasetImporter(100) self.assertRaises(UnknownPathError, client.upload, u'user', [{ 'about': u'hello world', 'values': { u'foo/bar': 13 } }]) def testUploadWithPermissionViolation(self): """L{DatasetImporter.upload} checks permissions when importing data.""" UserAPI().create([(u'user1', u'pwd', u'User 1', u'*****@*****.**')]) client = DatasetImporter(100) self.assertRaises(PermissionDeniedError, client.upload, u'user', [{ 'about': u'hello world', 'values': { u'user1/bar': 13 } }]) def testUploadWithUnknownUser(self): """ L{DatasetImporter.upload} raises an L{UnknownUserError} if the specified L{User} doesn't exist. """ client = DatasetImporter(100) self.assertRaises(UnknownUserError, client.upload, u'unknown', [{ 'about': u'hello world', 'values': { u'unknown/bar': 13 } }]) def testUploadLogsMessage(self): """ Uploads must prefix log output with the passed message. """ client = DatasetImporter(100) client.upload(u'user', [{ 'about': u'hello world', 'values': { u'user/bar': 13 } }], 'message-xxx') self.assertTrue(self.log.getvalue().startswith( 'message-xxx: Importing 1 new objects.\n' 'message-xxx: Imported 1/1 new objects.\n' 'message-xxx: Imported 1 objects in '))
class FacadeObjectMixinTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(FacadeObjectMixinTest, self).setUp() self.system = createSystemData() self.transact = Transact(self.threadPool) factory = FluidinfoSessionFactory('API-9000') self.facade = Facade(self.transact, factory) UserAPI().create([(u'username', u'password', u'User', u'*****@*****.**')]) self.user = getUser(u'username') self.permissions = CachingPermissionAPI(self.user) @inlineCallbacks def testCreateObjectPermissionDenied(self): """ L{FacadeObjectMixin.createObject} raises a L{TUnauthorized} exception if the user is the anonymous user. """ self.store.commit() with login(u'anon', uuid4(), self.transact) as session: deferred = self.facade.createObject(session) yield self.assertFailure(deferred, TUnauthorized) @inlineCallbacks def testCreateObjectWithoutAboutValue(self): """ L{FacadeObjectAPI.createObject} always returns a valid C{UUID} in a C{str} for a new object ID if an about value is not given. """ self.store.commit() with login(u'username', uuid4(), self.transact) as session: objectID = yield self.facade.createObject(session) self.assertEqual(objectID, str(UUID(objectID))) @inlineCallbacks def testCreateObjectWithAboutValue(self): """ L{FacadeObjectAPI.createObject} always returns a valid C{UUID} in a C{str} for a new object ID if an about value is given and it doesn't already exist. """ self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: objectID = yield self.facade.createObject(session, about='foo') objectID = UUID(objectID) self.store.rollback() value = getAboutTagValues([objectID], [u'foo']).one() self.assertEqual(u'foo', value.value) tag = self.system.tags[u'fluiddb/about'] value = getTagValues(values=[(objectID, tag.id)]).one() self.assertIdentical(self.system.users[u'fluiddb'], value.creator) @inlineCallbacks def testCreateObjectWithDuplicateAboutValue(self): """ L{FacadeObjectAPI.createObject} returns a valid C{UUID} in a C{str} for an existing object ID if an about value is given and already exists in the database. """ objectID = uuid4() createAboutTagValue(objectID, u'bar') values = {objectID: {u'fluiddb/about': u'bar'}} SecureTagValueAPI(self.system.superuser).set(values) self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: resultObjectID = yield self.facade.createObject(session, about='bar') self.assertEqual(str(objectID), resultObjectID) @inlineCallbacks def testGetObject(self): """ L{FacadeObjectAPI.getObject} returns a L{TObjectInfo} with the L{Tag.path}s for which the L{User} has L{Operation.READ_TAG_VALUE}. """ objectID = uuid4() SecureTagAPI(self.user).create([(u'username/foo', u'A description')]) values = {objectID: {u'username/foo': u'bar'}} SecureTagValueAPI(self.user).set(values) self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: objectInfo = yield self.facade.getObject(session, str(objectID)) self.assertEqual([u'username/foo'], objectInfo.tagPaths) self.assertEqual(None, objectInfo.about) @inlineCallbacks def testGetObjectWithAboutValue(self): """ L{FacadeObjectAPI.getObject} returns a L{TObjectInfo} with the L{Tag.path}s for which the L{User} has L{Operation.READ_TAG_VALUE}, and the L{AboutTagValue.value} if it has one and C{showAbout} is C{True}. """ objectID = uuid4() SecureTagAPI(self.user).create([(u'username/foo', u'A description')]) values = {objectID: {u'username/foo': u'bar'}} SecureTagValueAPI(self.user).set(values) aboutTag = self.system.tags[u'fluiddb/about'] createAboutTagValue(objectID, u'bar') createTagValue(self.user.id, aboutTag.id, objectID, u'about value') self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: objectInfo = yield self.facade.getObject(session, str(objectID), showAbout=True) self.assertEqual([u'fluiddb/about', u'username/foo'], sorted(objectInfo.tagPaths)) self.assertEqual('about value', objectInfo.about) @inlineCallbacks def testGetObjectWithAboutValueDoesNotExist(self): """ L{FacadeObjectAPI.getObject} returns a L{TObjectInfo} with the L{Tag.path}s for which the L{User} has L{Operation.READ_TAG_VALUE}, and the L{AboutTagValue.value} if it has one and C{showAbout} is C{True}. """ objectID = uuid4() tag = createTag(self.user, self.user.namespace, u'foo') createTagPermission(tag) createTagValue(self.user.id, tag.id, objectID, u'bar') session = AuthenticatedSession(self.user.username, uuid4()) self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: objectInfo = yield self.facade.getObject(session, str(objectID), showAbout=True) self.assertEqual([u'username/foo'], objectInfo.tagPaths) self.assertIdentical(None, objectInfo.about) @inlineCallbacks def testGetObjectDeniedPaths(self): """ L{FacadeObjectAPI.getObject} returns a L{TObjectInfo} with the L{Tag.path}s for which the L{User} has L{Operation.READ_TAG_VALUE}. """ SecureTagAPI(self.user).create([(u'username/tag1', u'description'), (u'username/tag2', u'description')]) objectID = uuid4() values = { objectID: { u'username/tag1': 16 }, objectID: { u'username/tag2': 16 } } SecureTagValueAPI(self.user).set(values) self.permissions.set([ (u'username/tag1', Operation.READ_TAG_VALUE, Policy.CLOSED, []), (u'username/tag2', Operation.READ_TAG_VALUE, Policy.OPEN, []) ]) self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: objectInfo = yield self.facade.getObject(session, str(objectID)) self.assertEqual([u'username/tag2'], objectInfo.tagPaths) self.assertEqual(None, objectInfo.about) @inlineCallbacks def testGetObjectAllPathsAreDenied(self): """ L{FacadeObjectAPI.getObject} returns a L{TObjectInfo} with the L{Tag.path}s for which the L{User} has L{Operation.READ_TAG_VALUE}, if all of them are denied, L{FacadeObjectAPI.getObject} must return an empty L{TObjectInfo}. """ SecureTagAPI(self.user).create([(u'username/tag1', u'description'), (u'username/tag2', u'description')]) objectID = uuid4() values = { objectID: { u'username/tag1': 16 }, objectID: { u'username/tag2': 16 } } SecureTagValueAPI(self.user).set(values) self.permissions.set([ (u'username/tag1', Operation.READ_TAG_VALUE, Policy.CLOSED, []), (u'username/tag2', Operation.READ_TAG_VALUE, Policy.CLOSED, []) ]) self.store.commit() with login(self.user.username, uuid4(), self.transact) as session: objectInfo = yield self.facade.getObject(session, str(objectID)) self.assertEqual([], objectInfo.tagPaths) self.assertEqual(None, objectInfo.about)
class JSONRPCResourceTest(FluidinfoTestCase): resources = [('log', LoggingResource())] @inlineCallbacks def testIncorrectContentLength(self): """ An incorrect content-length headers causes a JSONRPC_PARSE_ERROR error which is logged. """ headers = Headers({'Content-Length': ['100'], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Invalid payload: ContentLengthMismatch.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('<Payload empty or unparseable>', self.log.getvalue()) @inlineCallbacks def testMissingContentLength(self): """ A missing Content-Length header causes a JSONRPC_PARSE_ERROR error which is logged. """ headers = Headers({'Content-Type': ['application/json']}) request = FakeRequest(headers=headers) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Missing Content-Length header or empty payload.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('<Payload empty or unparseable>', self.log.getvalue()) @inlineCallbacks def testMissingContentType(self): """ A missing Content-Type header causes a JSONRPC_PARSE_ERROR error which is logged. """ headers = Headers({'Content-Length': ['0']}) request = FakeRequest(headers=headers) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Missing Content-Type header.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('<Payload empty or unparseable>', self.log.getvalue()) @inlineCallbacks def testUnparseableContentType(self): """ A malformed Content-Type header causes a JSONRPC_PARSE_ERROR error which is logged. """ headers = Headers({'Content-Type': ['papier-mache']}) request = FakeRequest(headers=headers) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Unparseable Content-Type header.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('<Payload empty or unparseable>', self.log.getvalue()) @inlineCallbacks def testUnknownContentType(self): """ An unknown Content-Type header causes a JSONRPC_PARSE_ERROR error. which is logged. """ headers = Headers({'Content-Length': ['0'], 'Content-Type': ['text/papier-mache']}) request = FakeRequest(headers=headers) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Unknown Content-Type.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('<Payload empty or unparseable>', self.log.getvalue()) @inlineCallbacks def testIncorrectContentMD5(self): """ An incorrect Content-MD5 header causes a JSONRPC_PARSE_ERROR error which is logged. """ body = dumps({'something': 42}) headers = Headers({'Content-Length': [str(len(body))], 'Content-MD5': ['omg'], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Invalid payload: ContentChecksumMismatch.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('<Payload empty or unparseable>', self.log.getvalue()) @inlineCallbacks def testNonJSONPayload(self): """ A non-JSON payload causes a JSONRPC_PARSE_ERROR error which is logged. """ body = 'Invalid JSON' headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Payload was not valid JSON.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('Request payload: Invalid JSON.', self.log.getvalue()) @inlineCallbacks def testNonJSONObjectPayload(self): """ A JSON payload that's not an object causes a JSONRPC_PARSE_ERROR error which is logged. """ body = dumps('Not a dict') headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_PARSE_ERROR, response['error']['code']) message = 'Payload was not a JSON object.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('Request payload: "Not a dict".', self.log.getvalue()) @inlineCallbacks def testPayloadWithNoRequestId(self): """ A payload with no id causes a JSONRPC_INVALID_REQUEST error which is logged. The JSON RPC spec actually allows a request to not have an id, in which case it's a "notification". Let's relax our code when/if we get to the point of sending notifications (which don't require responses). """ body = dumps({}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_INVALID_REQUEST, response['error']['code']) message = "Request had no 'id' argument." self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('Request payload: {}.', self.log.getvalue()) @inlineCallbacks def testPayloadWithNoJSONRPCVersion(self): """ A payload with no JSON RPC version causes a JSONRPC_INVALID_REQUEST error which is logged. """ body = dumps({'id': 100}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_INVALID_REQUEST, response['error']['code']) message = "Request had no 'jsonrpc' argument." self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) self.assertIn('Request payload: {"id": 100}.', self.log.getvalue()) @inlineCallbacks def testPayloadWithIncorrectJSONRPCVersion(self): """ A payload with an incorrect JSON RPC version causes a JSONRPC_INVALID_REQUEST error which is logged. """ body = dumps({'id': 100, 'jsonrpc': '1.0'}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_INVALID_REQUEST, response['error']['code']) message = 'Only JSON RPC version 2.0 is supported.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) @inlineCallbacks def testPayloadWithNoMethodName(self): """ A payload with no method name causes a JSONRPC_INVALID_REQUEST error which is logged. """ body = dumps({'id': 100, 'jsonrpc': '2.0'}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_INVALID_REQUEST, response['error']['code']) message = "Request had no 'method' argument." self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) @inlineCallbacks def testPayloadWithUnknownMethod(self): """ A payload with a method that's unknown causes a JSONRPC_METHOD_NOT_FOUND error which is logged. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'bagpipes'}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_METHOD_NOT_FOUND, response['error']['code']) message = "Unknown method u'bagpipes'." self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) @inlineCallbacks def testPayloadWithNonDictNonListParams(self): """ A payload with a method that has a params argument that is not a C{dict} or a C{list} causes a JSONRPC_INVALID_PARAMS error which is logged. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'pass', 'params': 6}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(JSONRPC_INVALID_PARAMS, response['error']['code']) message = 'Params not an object or a list.' self.assertEqual(message, response['error']['message']) self.assertIn(message, self.log.getvalue()) @inlineCallbacks def testSimpleEchoMethodReturnsId(self): """ A successful method call should include the id from the request in its body. """ body = dumps({'id': 300, 'jsonrpc': '2.0', 'method': 'pass', 'params': [39, 'steps']}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(300, response['id']) @inlineCallbacks def testSimpleEchoMethodReturnsVersion(self): """ A successful method call should have the JSON RPC version in its body. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'pass', 'params': [39, 'steps']}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual('2.0', response['jsonrpc']) @inlineCallbacks def testSimpleEchoMethodWithListOfArgs(self): """A called method should be passed C{list} params.""" body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'pass', 'params': [39, 'steps']}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual({'args': [39, 'steps'], 'kwargs': {}}, response['result']) @inlineCallbacks def testSimpleEchoMethodWithKeywordArgs(self): """A called method should be passed C{dict} params.""" body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'pass', 'params': {'ingredient1': 'sugar', 'ingredient2': 'spice'}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual({'args': [], 'kwargs': {'ingredient1': 'sugar', 'ingredient2': 'spice'}}, response['result']) @inlineCallbacks def testSimpleFailingMethodReturnsId(self): """ A failing method call should include the id from the request in its body. """ body = dumps({'id': 300, 'jsonrpc': '2.0', 'method': 'fail', 'params': [39, 'steps']}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual(300, response['id']) @inlineCallbacks def testSimpleFailingMethodReturnsVersion(self): """ A failing method call should have the JSON RPC version in its body. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': [39, 'steps']}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual('2.0', response['jsonrpc']) @inlineCallbacks def testSimpleFailingMethodReturnsErrorWithCodeAndMessage(self): """ A failing method should return an error dictionary containing a code and a message. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertTrue('code' in response['error']) self.assertTrue('message' in response['error']) @inlineCallbacks def testSimpleFailingMethodReturnsRequestIDInResponseHeader(self): """A failing method returns an X-FluidDB-Request-Id HTTP header.""" body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) yield resource.deferred_render_POST(request) self.assertTrue(request.responseHeaders.hasHeader( 'X-FluidDB-Request-Id')) @inlineCallbacks def testSimpleFailingMethodLogsJSONRPCError(self): """A failing method logs a JSON RPC error.""" body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) yield resource.deferred_render_POST(request) self.assertIn('JSON RPC error.', self.log.getvalue()) @inlineCallbacks def testSimpleFailingMethodLogsTheRequestPayload(self): """A failing method logs the request payload.""" body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) yield resource.deferred_render_POST(request) self.assertIn('Request payload: ', self.log.getvalue()) @inlineCallbacks def testLongRequestPayloadIsTruncatedInErrorLog(self): """ A failing call that sends a long payload must have the request payload truncated in the log. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {'long': '+' * 1000}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) yield resource.deferred_render_POST(request) self.assertIn('... <payload truncated for logging>', self.log.getvalue()) @inlineCallbacks def testSimpleFailingMethodLogsTheResponsePayload(self): """A failing method logs the response payload.""" body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) yield resource.deferred_render_POST(request) self.assertIn('Response payload: ', self.log.getvalue()) @inlineCallbacks def testFailureReturnsInternalErrorCode(self): """ A method that raises an exception results in a C{JSONRPC_INTERNAL_ERROR} error code. A message is written to the log. """ body = dumps({'id': 100, 'jsonrpc': '2.0', 'method': 'fail', 'params': {}}) headers = Headers({'Content-Length': [str(len(body))], 'Content-Type': ['application/json']}) request = FakeRequest(headers=headers, body=body) resource = TestResource(None, None) result = yield resource.deferred_render_POST(request) response = loads(result) self.assertEqual({'code': JSONRPC_INTERNAL_ERROR, 'message': 'Internal error.'}, response['error']) self.assertIn('exceptions.RuntimeError', self.log.getvalue())
class VerifyUserPasswordResourceTest(FluidinfoTestCase): resources = [('cache', CacheResource()), ('config', ConfigResource()), ('log', LoggingResource()), ('store', DatabaseResource()), ('threadPool', ThreadPoolResource())] def setUp(self): super(VerifyUserPasswordResourceTest, self).setUp() self.transact = Transact(self.threadPool) createSystemData() UserAPI().create([ (u'fluidinfo.com', 'secret', u'Fluidinfo', u'*****@*****.**'), (u'user', u'pass', u'Peter Parker', u'*****@*****.**')]) consumer = getUser(u'anon') OAuthConsumerAPI().register(consumer) self.store.commit() @defer.inlineCallbacks def testPostWithCorrectPasswordReturnsCorrectKeys(self): """ A C{POST} to C{/users/user/verify} with the correct password returns a JSON object with all the expected keys, including valid = True. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.OK) result = loads(request.response) self.assertEqual( ['accessToken', 'fullname', 'renewalToken', 'role', 'valid'], sorted(result.keys())) self.assertTrue(result['valid']) @defer.inlineCallbacks def testPostWithCorrectPasswordDoesNotCauseALogWarning(self): """ A C{POST} to C{/users/user/verify} with the correct password should not cause a complaint about unknown return payload fields in the logging system. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred logOutput = self.log.getvalue() self.assertNotIn("unknown response payload field 'renewalToken'", logOutput) self.assertNotIn("unknown response payload field 'accessToken'", logOutput) @defer.inlineCallbacks def testPostWithCorrectPasswordReturnsCorrectRole(self): """ A C{POST} to C{/users/user/verify} with the correct password returns a JSON object with the correct user role. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.OK) result = loads(request.response) self.assertEqual('USER', result['role']) @defer.inlineCallbacks def testPostWithCorrectPasswordReturnsCorrectFullname(self): """ A C{POST} to C{/users/user/verify} with the correct password returns a JSON object with the user's correct full name. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.OK) result = loads(request.response) self.assertEqual(u'Peter Parker', result['fullname']) @defer.inlineCallbacks def testPostWithIncorrectPasswordReturnsFalse(self): """ A C{POST} to C{/users/user/verify} with the incorrect password returns a C{{'valid': False}} response. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.OK) self.assertEqual(loads(request.response), {'valid': False}) @defer.inlineCallbacks def testPostWithUnknownUsernameReturnsNotFound(self): """ A C{POST} to C{/users/user/verify} with an unknown username returns a 404 Not Found. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'unknown') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.NOT_FOUND) @defer.inlineCallbacks def testPostWithoutPasswordReturnsBadRequest(self): """ A C{POST} to C{/users/user/verify} without a password returns a 400 Bad Request. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = '' headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.BAD_REQUEST) @defer.inlineCallbacks def testPostWithExtraCrapInPayloadReturnsBadRequest(self): """ A C{POST} to C{/users/user/verify} with unexpected data in the payload returns a 400 Bad Request. """ with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******', 'foo': 'bar'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json'], 'X-Forwarded-Protocol': ['https']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.BAD_REQUEST) @defer.inlineCallbacks def testInsecurePostIsRejected(self): """A C{POST} via HTTP is rejected if not in development mode.""" self.config.set('service', 'development', 'false') with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = '' headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.BAD_REQUEST) self.assertEqual( request.getResponseHeader('X-FluidDB-Message'), '/users/<username>/verify requests must use HTTPS') @defer.inlineCallbacks def testInsecurePostIsNotRejectedInDevelopmentMode(self): """A C{POST} via HTTP is not rejected when in development mode.""" self.config.set('service', 'development', 'true') with login(None, None, self.transact) as session: resource = VerifyUserPasswordResource(None, session, 'user') payload = dumps({'password': '******'}) headers = {'Content-Length': [str(len(payload))], 'Content-Type': ['application/json']} request = FakeRequest(method='POST', headers=Headers(headers), body=payload) self.assertEqual(NOT_DONE_YET, resource.render(request)) yield resource.deferred self.assertEqual(request.code, http.OK)