def test_github_signature_fail(self): """ Error if we're to check github signatures, but they're missing entirely """ session = MockPublisherSession(self) resource = WebhookResource( options={ u"topic": u"com.test.webhook", u"github_secret": u"deadbeef", }, session=session, ) request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": []}, body=b'{"foo": "has happened"}') self.assertEqual(len(session._published_messages), 0) self.assertEqual(request.code, 400) received_json = json.loads(request.get_written_data().decode('utf8')) self.assertEqual( received_json, { "error": "Malformed request to the REST bridge.", "args": [], "kwargs": {} } )
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_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 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_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_github_signature_invalid(self): """ A correctly-formatted GitHub signature, but it's invalid """ session = MockPublisherSession(self) resource = WebhookResource( options={ "topic": "com.test.webhook", "github_secret": "deadbeef", }, session=session, ) request = yield renderResource(resource, b"/", method=b"POST", headers={b"Content-Type": []}, body=b'{"foo": "has happened"}') self.assertEqual(len(session._published_messages), 0) self.assertEqual(request.code, 400) received_json = json.loads(request.get_written_data().decode('utf8')) self.assertEqual(received_json, { "error": "Malformed request to the REST bridge.", "args": [], "kwargs": {}, })
def test_github_signature_valid(self): """ A correctly-formatted GitHub signature """ session = MockPublisherSession(self) resource = WebhookResource( options={ u"topic": u"com.test.webhook", u"github_secret": github_test_token, }, session=session, ) yield renderResource( resource, b"/webhook", method=b"POST", headers={ b"Content-Type": [], b"X-Hub-Signature": [b"sha1=5054d1d2e6f5d293fbea8fdeed5117f2854ccf7a"], }, body=github_request_data, ) self.assertEqual(len(session._published_messages), 1) msg = session._published_messages[0] data = msg['args'][0] self.assertEqual( data['body'].encode('utf8'), github_request_data.strip(), )
def test_basic(self): """ A message, when a request has gone through to it, publishes a WAMP message on the configured topic. """ session = MockPublisherSession(self) resource = WebhookResource({u"topic": u"com.test.webhook"}, session) with LogCapturer() as l: request = yield renderResource( resource, b"/", method=b"POST", headers={b"Content-Type": []}, body=b'{"foo": "has happened"}') self.assertEqual(len(session._published_messages), 1) self.assertEqual( { u"body": u'{"foo": "has happened"}', u"headers": { u"Content-Type": [], u'Date': [u'Sun, 1 Jan 2013 15:21:01 GMT'], u'Host': [u'localhost:8000'] } }, session._published_messages[0]["args"][0]) self.assertEqual(request.code, 202) self.assertEqual(request.get_written_data(), b"OK") logs = l.get_category("AR201") self.assertEqual(len(logs), 1) self.assertEqual(logs[0]["code"], 202)
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_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_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_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_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_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_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_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_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_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_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_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_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_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)