def assertXFluidDBHeaderForType(self, method, value, expectedTypeString): """ Helper method to check if a resource is returning the appropriate C{X-FluidDB-Type} header for a given HTTP method. @param method: The HTTP method to use. Should be 'GET' or 'HEAD'. @param value: The value to test. @param expectedTypeString: The expected string that should be returned by C{X-FluidDB-Type}. """ facadeClient = FakeFacade() session = FakeSession() # Tell our FakeFacade to preload some data for a given tag. facadeClient.values = { 'fe2f50c8-997f-4049-a180-9a37543d001d': { 'tag/test': value}} resource = TagInstanceResource(facadeClient, session, 'fe2f50c8-997f-4049-a180-9a37543d001d', 'tag/test') request = FakeRequest(method=method) yield getattr(resource, 'render_' + method)(request) typeValue = request.getResponseHeader(buildHeader('Type')) self.assertEqual(expectedTypeString, typeValue)
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)
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)
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)
def testRenderRecentUsersActivity(self): """ L{RecentUsersActivityResource.deferred_render_GET} renders a response with recent activity data by the users 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': ['fluiddb/users/username = "******"']}) with login(u'username', self.user.objectID, self.transact) as session: resource = RecentUsersActivityResource(self.facade, session) body = yield resource.deferred_render_GET(request) body = json.loads(body) expected = [{ u'about': u'object1', u'id': str(objectID), u'tag': u'username/tag1', u'username': u'username', 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)
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)
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)
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')
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)
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 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 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 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())
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'))
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 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)
def testRequestWithoutHeader(self): """ Non-HTTPS requests (with no C{X-Forwarded-Protocol} header) raise a L{LoginFailed} error. """ request = FakeRequest(path='/', uri='https://example.org/xx?arg=1', headers=Headers( {'X-FluidDB-Access-Token': ['xxx']})) response = b64encode('anon:anon') self.assertRaises(LoginFailed, self.credentialFactory.decode, response, request)
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'])
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)
def testDumpsAndLoads(self): """ Data stored by an L{HTTPPlugin} can be dumped to and loaded from JSON. """ headers = Headers({'x-foo': ['bar']}) request = FakeRequest(uri='/objects', method='PUT', path='/objects?foo=bar', headers=headers) session = SampleSession('id', self.transact) session.start() try: session.http.trace(request) request.setHeader('foo', 'bar') request.setResponseCode(FORBIDDEN) finally: session.stop() data = session.dumps() loadedSession = SampleSession('another-id', self.transact) loadedSession.loads(data) self.assertEqual(loadedSession.http.uri, session.http.uri) self.assertEqual(loadedSession.http.path, session.http.path) self.assertEqual(loadedSession.http.method, session.http.method) self.assertEqual(loadedSession.http.requestHeaders, session.http.requestHeaders) self.assertEqual(loadedSession.http.responseHeaders, session.http.responseHeaders) self.assertEqual(loadedSession.http.code, session.http.code)
def testTrace(self): """ L{HTTPPlugin.trace} extracts and stores information from a request instance. """ headers = Headers({'x-hello': ['goodbye']}) request = FakeRequest(uri='/objects', method='PUT', path='/objects?foo=bar', headers=headers) session = SampleSession('id', self.transact) session.start() try: session.http.trace(request) request.setHeader('foo', 'bar') request.setResponseCode(FORBIDDEN) finally: session.stop() self.assertEqual('/objects', session.http.uri) self.assertEqual('/objects?foo=bar', session.http.path) self.assertEqual('PUT', session.http.method) self.assertEqual(headers, session.http.requestHeaders) self.assertEqual({'Foo': ['bar']}, dict(session.http.responseHeaders.getAllRawHeaders())) self.assertEqual(FORBIDDEN, session.http.code)
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']))
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'])
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)
def testRenderRecentAboutActivityWithNonexistentAboutValue(self): """ L{RecentAboutActivityResource.deferred_render_GET} renders an empty list if the given object doesn't exist. """ self.store.commit() request = FakeRequest() with login(u'username', self.user.objectID, self.transact) as session: resource = RecentAboutActivityResource(self.facade, session, u'unknown') body = yield resource.deferred_render_GET(request) body = json.loads(body) self.assertEqual([], body) self.assertEqual(http.OK, request.code)
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())
def testRenderRecentObjectActivityWithNoQuery(self): """ L{RecentUsersActivityResource.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 = RecentUsersActivityResource(self.facade, session) deferred = resource.deferred_render_GET(request) yield self.assertFailure(deferred, MissingArgument)
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)
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'])
def testDecodeWithUnsplittableBasicAuthCredentials(self): """ L{OAuth2CredentialFactory.decode} raises L{LoginFailed} if passed Basic Auth credentials that cannot be split on ':'. """ response = 'unsplittable' request = FakeRequest(path='/', uri='https://example.org/xx?arg=1', headers=Headers({ 'X-FluidDB-Access-Token': ['xxx'], 'X-Forwarded-Protocol': ['https'] })) self.assertRaises(LoginFailed, self.credentialFactory.decode, response, request)