예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
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)
예제 #4
0
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)
예제 #5
0
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()
예제 #6
0
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)
예제 #7
0
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())
예제 #8
0
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'))
예제 #9
0
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)
예제 #10
0
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)
예제 #11
0
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)
예제 #12
0
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)
예제 #13
0
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')))
예제 #14
0
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)
예제 #15
0
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)
예제 #16
0
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)
예제 #17
0
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)
예제 #18
0
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())
예제 #19
0
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)
예제 #20
0
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)
예제 #21
0
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 '))
예제 #22
0
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))
예제 #23
0
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)
예제 #24
0
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)
예제 #25
0
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())
예제 #26
0
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)
예제 #27
0
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 '))
예제 #28
0
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)
예제 #29
0
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())
예제 #30
0
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)