def test_outdated_delta(self): """ If the delta between now and the timestamp in the request is larger than C{timestamp_delta_limit}, the request is rejected. """ custOpts = {"timestamp_delta_limit": 1} custOpts.update(resourceOptions) session = MockPublisherSession(self) resource = PublisherResource(custOpts, session) signedParams = makeSignedArguments({}, "bazapp", "foobar", publishBody) signedParams[b'timestamp'] = [b"2011-10-14T16:59:51.123Z"] with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, params=signedParams) self.assertEqual(request.code, 400) errors = l.get_category("AR464") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_broken_contenttype(self): """ Crossbar rejects broken content-types. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=blarg;charset=boo"]}, body=b'{"foo": "\xe2\x98\x83"}')) errors = l.get_category("AR450") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400) del l with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"charset=blarg;application/json"]}, body=b'{"foo": "\xe2\x98\x83"}')) self.assertEqual(request.code, 400) errors = l.get_category("AR452") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_publish_needs_topic(self): """ Test that attempted publishes without a topic will be rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{}') self.assertEqual(len(session._published_messages), 0) self.assertEqual(request.code, 400) errors = l.get_category("AR455") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400) self.assertEqual( json.loads(native_string(request.get_written_data())), { "error": log_categories["AR455"].format(key="topic"), "args": [], "kwargs": {} })
def test_undecodable_UTF8(self): """ Undecodable UTF-8 will return an error. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf( renderResource( resource, b"/", method=b"POST", headers={ b"Content-Type": [b"application/json;charset=utf-8"] }, body= b'{"topic": "com.test.messages", "args": ["\x61\x62\x63\xe9"]}' )) self.assertEqual(request.code, 400) errors = l.get_category("AR451") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_basic_publish(self): """ Test a very basic publish to a topic. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": [1]}') self.assertEqual(len(session._published_messages), 1) self.assertEqual(session._published_messages[0]["args"], (1, )) self.assertEqual(request.code, 200) logs = l.get_category("AR200") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) self.assertEqual(json.loads(native_string(request.get_written_data())), {"id": session._published_messages[0]["id"]}) # ensure we have all the format-keys AR200 asks for (can we # extract these from the _log_categories string instead?) self.assertIn('code', logs[0]) self.assertIn('reason', logs[0])
def create(transport, path, config): personality = transport.worker.personality personality.WEB_SERVICE_CHECKERS['publisher'](personality, config) # create a vanilla session: the publisher will use this to inject events # publisher_session_config = ComponentConfig(realm=config['realm'], extra=None) publisher_session = ApplicationSession(publisher_session_config) # add the publisher session to the router # router = transport._worker._router_session_factory._routerFactory._routers[ config['realm']] transport._worker._router_session_factory.add(publisher_session, router, authrole=config.get( 'role', 'anonymous')) # now create the publisher Twisted Web resource # resource = PublisherResource(config.get('options', {}), publisher_session) return RouterWebServiceRestPublisher(transport, path, config, resource)
def test_required_tls_with_tls(self): """ Required TLS, plus a request over TLS, will allow the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_tls": True}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, isSecure=True)) self.assertEqual(request.code, 200)
def test_allowed_IP_range(self): """ The client having an IP in an allowed address range allows the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_ip": ["127.0.0.0/8"]}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 200)
def test_disallowed_IP_range(self): """ The client having an IP not in allowed address range denies the request. """ session = MockPublisherSession(self) resource = PublisherResource({"require_ip": ["192.168.0.0/16", "10.0.0.0/8"]}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 400) self.assertIn(b"Request denied based on IP address", request.get_written_data())
def test_UTF8_assumption(self): """ A body, when the Content-Type has no charset, is assumed to be UTF-8. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": ["\xe2\x98\x83"]}')) self.assertEqual(request.code, 200) self.assertIn(b'{"id":', request.get_written_data())
def test_allow_caps_in_content_type(self): """ Differently-capitalised content-type headers will be allowed. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"CONTENT-TYPE": [b"APPLICATION/JSON"]}, body=publishBody)) self.assertEqual(request.code, 200) self.assertIn(b'{"id":', request.get_written_data())
def test_allow_charset_in_content_type(self): """ A charset in the content-type will be allowed. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json; charset=utf-8"]}, body=publishBody)) self.assertEqual(request.code, 200) self.assertIn(b'{"id":', request.get_written_data())
def test_bad_method(self): """ An incorrect method will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"BLBLBLB", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 405) errors = l.get_category("AR405") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 405)
def test_JSON_list_body(self): """ A body that is not a JSON dict will be rejected by the server. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b"[{},{}]")) self.assertEqual(request.code, 400) errors = l.get_category("AR454") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_too_large_body(self): """ A too large body will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({"post_body_limit": 1}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody)) self.assertEqual(request.code, 413) errors = l.get_category("AR413") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 413)
def test_ASCII_denied(self): """ A body with an ASCII charset is denied, it must be UTF-8. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json; charset=ascii"]}, body=b'')) self.assertEqual(request.code, 400) errors = l.get_category("AR450") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_publish_cberror(self): """ A publish that errors with a Crossbar failure will return a generic error to the client and log the exception. """ class RejectingPublisherSession(object): """ A mock WAMP session. """ def publish(self, topic, *args, **kwargs): return maybeDeferred(self._publish, topic, *args, **kwargs) def _publish(self, topic, *args, **kwargs): raise ValueError("ono") session = RejectingPublisherSession() resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": [1]}') self.assertEqual(request.code, 500) logs = l.get_category("AR456") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 500) logs = l.get_category("AR500") self.assertEqual(len(logs), 1) self.assertEqual( json.loads(native_string(request.get_written_data())), { "error": "wamp.error.runtime_error", "args": ["Sorry, Crossbar.io has encountered a problem."], "kwargs": {} }) # We manually logged it, so this one is OK self.flushLoggedErrors(ValueError)
def test_unknown_key(self): """ An unknown key in a request should mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource(resourceOptions, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, sign=True, signKey="spamapp", signSecret="foobar") self.assertEqual(request.code, 401) errors = l.get_category("AR460") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 401)
def test_multiple_content_length(self): """ Requests with multiple Content-Length headers will be rejected. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"], b"Content-Length": ["1", "10"]}, body=publishBody)) self.assertEqual(request.code, 400) errors = l.get_category("AR463") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_unknown_encoding(self): """ A body, when the Content-Type has been set to something other than charset=utf-8, will error out. """ session = MockPublisherSession(self) resource = PublisherResource({}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json;charset=blarg"]}, body=b'{"args": ["\x61\x62\x63\xe9"]}')) self.assertEqual(request.code, 400) errors = l.get_category("AR450") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_good_signature(self): """ A valid, correct signature will mean the request is processed. """ session = MockPublisherSession(self) resource = PublisherResource(resourceOptions, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, sign=True, signKey="bazapp", signSecret="foobar") self.assertEqual(request.code, 200) self.assertEqual(json.loads(native_string(request.get_written_data())), {"id": session._published_messages[0]["id"]}) logs = l.get_category("AR203") self.assertEqual(len(logs), 1)
def test_not_matching_bodylength(self): """ A body length that is different than the Content-Length header will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource({"post_body_limit": 1}, session) with LogCapturer("debug") as l: request = self.successResultOf(renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"], b"Content-Length": [1]}, body=publishBody)) self.assertEqual(request.code, 400) errors = l.get_category("AR465") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_incorrect_secret(self): """ An incorrect secret (but an otherwise well-formed signature) will mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource(resourceOptions, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, sign=True, signKey="bazapp", signSecret="foobar2") self.assertEqual(request.code, 401) errors = l.get_category("AR459") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 401)
def test_wrong_seq(self): """ A missing sequence in a request should mean the request is rejected. """ session = MockPublisherSession(self) resource = PublisherResource(resourceOptions, session) signedParams = makeSignedArguments({}, "bazapp", "foobar", publishBody) signedParams[b'seq'] = [b"notaseq"] with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=publishBody, params=signedParams) self.assertEqual(request.code, 400) errors = l.get_category("AR462") self.assertEqual(len(errors), 1) self.assertEqual(errors[0]["code"], 400)
def test_publish_error(self): """ A publish that errors will return the error to the client. """ class RejectingPublisherSession(object): """ A mock WAMP session. """ def publish(self, topic, *args, **kwargs): return maybeDeferred(self._publish, topic, *args, **kwargs) def _publish(self, topic, *args, **kwargs): raise ApplicationError(u'wamp.error.not_authorized', foo="bar") session = RejectingPublisherSession() resource = PublisherResource({}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": [b"application/json"]}, body=b'{"topic": "com.test.messages", "args": [1]}') self.assertEqual(request.code, 200) logs = l.get_category("AR456") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 200) self.assertEqual( json.loads(native_string(request.get_written_data())), { "error": "wamp.error.not_authorized", "args": [], "kwargs": { "foo": "bar" } })